Merge remote-tracking branch 'alfresco-core/support/HF/7.33.N' into release/6.2.1

This commit is contained in:
Chris Shields
2020-07-21 10:45:03 +01:00
214 changed files with 29987 additions and 0 deletions

8
core/.gitattributes vendored Normal file
View File

@@ -0,0 +1,8 @@
.* eol=crlf
*.html eol=crlf
*.java eol=crlf
*.txt eol=crlf
*.css eol=crlf
*.xml eol=crlf
*.js eol=crlf
*.properties eol=crlf

4
core/.gitbugtraq Normal file
View File

@@ -0,0 +1,4 @@
# For SmartGit
[bugtraq "jira"]
url = https://issues.alfresco.com/jira/browse/%BUGID%
logRegex = ([A-Z]+-\\d+)

34
core/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
*.class
# Eclipse
.classpath
.settings
.project
# Intellij
.idea/
*.iml
*.iws
# Mac
.DS_Store
# Maven
target
*.log
*.log.*
# Mobile Tools for Java (J2ME)
.mtj
.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

10
core/.travis.settings.xml Normal file
View File

@@ -0,0 +1,10 @@
<settings>
<!-- required to push artifacts to repositories -->
<servers>
<server>
<id>alfresco-public</id>
<username>${env.MAVEN_USERNAME}</username>
<password>${env.MAVEN_PASSWORD}</password>
</server>
</servers>
</settings>

52
core/.travis.yml Normal file
View File

@@ -0,0 +1,52 @@
dist: trusty
sudo: required
language: java
jdk:
- openjdk11
cache:
directories:
- $HOME/.m2
branches:
only:
- master
- /support\/.*/
install: travis_retry mvn install -DskipTests=true -B -V
stages:
- test
- release
jobs:
include:
- stage: test
name: "Build and test"
script: travis_retry mvn test
- name: "WhiteSource scan"
# only on SP branches or master and if it is not a PR
if: fork = false AND (branch = master OR branch =~ /support\/SP\/.*/) AND type != pull_request
script:
# Download the latest version of WhiteSource Unified Agent
- curl -LJO https://github.com/whitesource/unified-agent-distribution/releases/latest/download/wss-unified-agent.jar
# Run WhiteSource Unified Agent
- java -jar wss-unified-agent.jar -apiKey ${WHITESOURCE_API_KEY} -c .wss-unified-agent.config
- name: "Source Clear Scan"
# only on SP branches or master and if it is not a PR
if: fork = false AND (branch = master OR branch =~ /support\/SP\/.*/) AND type != pull_request
script: skip
addons:
srcclr: true
- stage: release
name: "Push to Nexus"
if: fork = false AND (branch = master OR branch =~ /support\/.*/) AND type != pull_request AND commit_message !~ /\[no-release\]/
before_install:
- "cp .travis.settings.xml $HOME/.m2/settings.xml"
script:
# Use full history for release
- git checkout -B "${TRAVIS_BRANCH}"
# Add email to link commits to user
- git config user.email "${GIT_EMAIL}"
# Skip building of release commits
- mvn --batch-mode -q -DscmCommentPrefix="[maven-release-plugin][skip ci] " -Dusername="${GIT_USERNAME}" -Dpassword="${GIT_PASSWORD}" -DskipTests -Darguments=-DskipTests release:clean release:prepare release:perform

8
core/.whitesource Normal file
View File

@@ -0,0 +1,8 @@
{
"generalSettings": {
"shouldScanRepo": true
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure"
}
}

View File

@@ -0,0 +1,228 @@
####################################################################
# WhiteSource Unified-Agent configuration file
####################################################################
##########################################
# GENERAL SCAN MODE: Files and Package Managers
##########################################
checkPolicies=true
forceCheckAllDependencies=true
forceUpdate=true
forceUpdate.failBuildOnPolicyViolation=true
offline=false
#ignoreSourceFiles=true
#scanComment=
#updateInventory=false
#resolveAllDependencies=false
#failErrorLevel=ALL
#requireKnownSha1=false
#generateScanReport=true
#scanReportTimeoutMinutes=10
#excludeDependenciesFromNodes=.*commons-io.*,.*maven-model
#projectPerFolder=true
#projectPerFolderIncludes=
#projectPerFolderExcludes=
#wss.connectionTimeoutMinutes=60
# Change the below URL to your WhiteSource server.
# Use the 'WhiteSource Server URL' which can be retrieved
# from your 'Profile' page on the 'Server URLs' panel.
# Then, add the '/agent' path to it.
wss.url=https://saas.whitesourcesoftware.com/agent
#npm.resolveDependencies=false
#npm.ignoreSourceFiles=false
#npm.includeDevDependencies=true
#npm.runPreStep=true
#npm.ignoreNpmLsErrors=true
#npm.ignoreScripts=true
#npm.yarnProject=true
#npm.accessToken=
#npm.identifyByNameAndVersion=true
#bower.resolveDependencies=false
#bower.ignoreSourceFiles=true
#bower.runPreStep=true
#nuget.resolvePackagesConfigFiles=false
#nuget.resolveCsProjFiles=false
#nuget.resolveDependencies=false
#nuget.restoreDependencies=true
#nuget.ignoreSourceFiles=true
#nuget.runPreStep=true
#nuget.resolveNuspecFiles=false
#python.resolveDependencies=false
#python.ignoreSourceFiles=false
#python.ignorePipInstallErrors=true
#python.installVirtualenv=true
#python.resolveHierarchyTree=false
#python.requirementsFileIncludes=requirements.txt
#python.resolveSetupPyFiles=true
#python.runPipenvPreStep=true
#python.pipenvDevDependencies=true
#python.IgnorePipenvInstallErrors=true
#maven.ignoredScopes=test provided
maven.resolveDependencies=true
#maven.ignoreSourceFiles=true
#maven.aggregateModules=true
maven.ignorePomModules=false
#maven.runPreStep=true
#maven.ignoreMvnTreeErrors=true
#maven.environmentPath=
#maven.m2RepositoryPath=
#gradle.ignoredScopes=
#gradle.resolveDependencies=false
#gradle.runAssembleCommand=false
#gradle.runPreStep=true
#gradle.ignoreSourceFiles=true
#gradle.aggregateModules=true
#gradle.preferredEnvironment=wrapper
#gradle.localRepositoryPath=
#paket.resolveDependencies=false
#paket.ignoredGroups=
#paket.ignoreSourceFiles=false
#paket.runPreStep=true
#paket.exePath=
#go.resolveDependencies=false
#go.collectDependenciesAtRuntime=true
#go.dependencyManager=
#go.ignoreSourceFiles=true
#go.glide.ignoreTestPackages=false
#go.gogradle.enableTaskAlias=true
#ruby.resolveDependencies = false
#ruby.ignoreSourceFiles = false
#ruby.installMissingGems = true
#ruby.runBundleInstall = true
#ruby.overwriteGemFile = true
#sbt.resolveDependencies=false
#sbt.ignoreSourceFiles=true
#sbt.aggregateModules=true
#sbt.runPreStep=true
#sbt.targetFolder=
#php.resolveDependencies=false
#php.runPreStep=true
#php.includeDevDependencies=true
#html.resolveDependencies=false
#cocoapods.resolveDependencies=false
#cocoapods.runPreStep=true
#cocoapods.ignoreSourceFiles=false
#hex.resolveDependencies=false
#hex.runPreStep=true
#hex.ignoreSourceFiles=false
#hex.aggregateModules=true
##################################
# Organization tokens:
##################################
apiKey=
#userKey is required if WhiteSource administrator has enabled "Enforce user level access" option
#userKey=
projectName=alfresco-core
projectVersion=
projectToken=
productName=ACS Community
productVersion=
productToken=
#updateType=APPEND
#requesterEmail=user@provider.com
#########################################################################################
# Includes/Excludes Glob patterns - PLEASE USE ONLY ONE EXCLUDE LINE AND ONE INCLUDE LINE
#########################################################################################
#includes=**/*.c **/*.cc **/*.cp **/*.cpp **/*.cxx **/*.c++ **/*.h **/*.hpp **/*.hxx
#includes=**/*.m **/*.mm **/*.js **/*.php
includes=**/*.jar
#includes=**/*.gem **/*.rb
#includes=**/*.dll **/*.cs **/*.nupkg
#includes=**/*.tgz **/*.deb **/*.gzip **/*.rpm **/*.tar.bz2
#includes=**/*.zip **/*.tar.gz **/*.egg **/*.whl **/*.py
## Exclude file extensions or specific directories by adding **/*.<extension> or **<excluded_dir>/**
excludes=**/*sources.jar **/*javadoc.jar
case.sensitive.glob=false
followSymbolicLinks=true
##################################
# Archive properties
##################################
#archiveExtractionDepth=2
#archiveIncludes=**/*.war **/*.ear
#archiveExcludes=**/*sources.jar
##################################
# Proxy settings
##################################
#proxy.host=
#proxy.port=
#proxy.user=
#proxy.pass=
##################################
# SCM settings
##################################
#scm.type=
#scm.user=
#scm.pass=
#scm.ppk=
#scm.url=
#scm.branch=
#scm.tag=
#scm.npmInstall=
#scm.npmInstallTimeoutMinutes=
#scm.repositoriesFile=
##############################################
# SCAN MODE: Linux package manager settings
##############################################
#scanPackageManager=true
##################################
# SCAN MODE: Docker images
##################################
#docker.scanImages=true
#docker.includes=.*.*
#docker.excludes=
#docker.pull.enable=true
#docker.pull.images=.*.*
#docker.pull.maxImages=10
#docker.pull.tags=.*.*
#docker.pull.digest=
#docker.delete.force=true
#docker.login.sudo=false
#docker.aws.enable=true
#docker.aws.registryIds=
##################################
# SCAN MODE: Docker containers
##################################
#docker.scanContainers=true
#docker.containerIncludes=.*.*
#docker.containerExcludes=
################################
# Serverless settings
################################
#serverless.provider=
#serverless.scanFunctions=true
#serverless.includes=
#serverless.excludes=
#serverless.region=
#serverless.maxFunctions=10

16
core/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,16 @@
### Contributing
Thanks for your interest in contributing to this project!
The following is a set of guidelines for contributing to this library. Most of them will make the life of the reviewer easier and therefore decrease the time required for the patch be included in the next version.
Because this project forms a part of Alfresco Content Services, the guidelines are hosted in the [Alfresco Social Community](http://community.alfresco.com/community/ecm) where they can be referenced from multiple projects.
Read an [overview on how this project is goverened](https://community.alfresco.com/docs/DOC-6385-project-overview-repository).
You can report an issue in the ALF project of the [Alfresco issue tracker](http://issues.alfresco.com).
Read [instructions for a good issue report](https://community.alfresco.com/docs/DOC-6263-reporting-an-issue).
Read [instructions for making a contribution](https://community.alfresco.com/docs/DOC-6269-submitting-contributions).
Please follow [the coding standards](https://community.alfresco.com/docs/DOC-4658-coding-standards).

165
core/LICENSE.txt Normal file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

42
core/README.md Normal file
View File

@@ -0,0 +1,42 @@
### Alfresco Core
[![Build Status](https://travis-ci.com/Alfresco/alfresco-core.svg?branch=master)](https://travis-ci.com/Alfresco/alfresco-core)
Alfresco Core is a library packaged as a jar file which is part of [Alfresco Content Services Repository](https://community.alfresco.com/docs/DOC-6385-project-overview-repository).
The library contains the following:
* Various helpers and utils
* Canned queries interface and supporting classes
* Generic encryption supporting classes
Version 7 of the library uses Spring 5, Quartz 2.3 and does not have Hibernate dependency.
### Building and testing
The project can be built and tested by running Maven command:
~~~
mvn clean install
~~~
### Artifacts
The artifacts can be obtained by:
* downloading from [Alfresco repository](https://artifacts.alfresco.com/nexus/content/groups/public)
* getting as Maven dependency by adding the dependency to your pom file:
~~~
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-core</artifactId>
<version>version</version>
</dependency>
~~~
and Alfresco repository:
~~~
<repository>
<id>alfresco-maven-repo</id>
<url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
</repository>
~~~
The SNAPSHOT version of the artifact is **never** published.
### Old version history
The history for older versions can be found in [Alfresco SVN](https://svn.alfresco.com/repos/alfresco-open-mirror/services/alfresco-core/)
### Contributing guide
Please use [this guide](CONTRIBUTING.md) to make a contribution to the project.

206
core/pom.xml Normal file
View File

@@ -0,0 +1,206 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-super-pom</artifactId>
<version>12</version>
</parent>
<artifactId>alfresco-core</artifactId>
<version>7.33</version>
<name>Alfresco Core</name>
<description>Alfresco core libraries and utils</description>
<scm>
<connection>scm:git:https://github.com/Alfresco/alfresco-core.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-core.git</developerConnection>
<url>https://github.com/Alfresco/alfresco-core</url>
<tag>7.33</tag>
</scm>
<distributionManagement>
<repository>
<id>alfresco-public</id>
<url>https://artifacts.alfresco.com/nexus/content/repositories/releases</url>
</repository>
</distributionManagement>
<properties>
<dependency.spring.version>5.1.15.RELEASE</dependency.spring.version>
<dependency.surf.version>7.14</dependency.surf.version>
<maven.build.sourceVersion>11</maven.build.sourceVersion>
</properties>
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1-HTTPCLIENT-1265</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.safehaus.jug</groupId>
<artifactId>jug</artifactId>
<version>2.0.0</version>
<classifier>asl</classifier>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160212</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${dependency.spring.version}</version>
<!-- exclude spring-jcl which is brought in by spring-core -->
<!-- see https://issues.alfresco.com/jira/browse/REPO-4774 -->
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-jcl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${dependency.spring.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
<!-- exclude c3p0 -->
<!-- see https://issues.alfresco.com/jira/browse/REPO-3447 -->
<exclusions>
<exclusion>
<groupId>com.mchange</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.alfresco.surf</groupId>
<artifactId>spring-surf-core-configservice</artifactId>
<version>${dependency.surf.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.guessencoding</groupId>
<artifactId>guessencoding</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.5</version>
</dependency>
<!-- provided dependencies -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!-- Test only dependencies, as popped up while running mvn test -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4-DBCP330</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<tagNameFormat>@{project.version}</tagNameFormat>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.api;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is used to denote a class or method as part
* of the public API. When a class or method is so designated then
* we will not change it within a release in a way that would make
* it no longer backwardly compatible with an earlier version within
* the release.
*
* @author Greg Melahn
*/
@Target( {ElementType.TYPE,ElementType.METHOD} )
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AlfrescoPublicApi
{
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Enumeration;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.StringUtils;
/**
* Simple extension to the{@link DefaultPropertiesPersister} to strip trailing whitespace
* from incoming properties.
*
* @author shane frensley
* @see org.springframework.util.DefaultPropertiesPersister
*/
public class AlfrescoPropertiesPersister extends DefaultPropertiesPersister
{
private static Log logger = LogFactory.getLog(AlfrescoPropertiesPersister.class);
@Override
public void load(Properties props, InputStream is) throws IOException
{
super.load(props, is);
strip(props);
}
@Override
public void load(Properties props, Reader reader) throws IOException
{
super.load(props, reader);
strip(props);
}
public void loadFromXml(Properties props, InputStream is) throws IOException
{
super.loadFromXml(props, is);
strip(props);
}
private void strip(Properties props)
{
for (Enumeration<Object> keys = props.keys(); keys.hasMoreElements();)
{
String key = (String) keys.nextElement();
String val = StringUtils.trimTrailingWhitespace(props.getProperty(key));
if (logger.isTraceEnabled())
{
logger.trace("Trimmed trailing whitespace for property " + key + " = " + val);
}
props.setProperty(key, val);
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.sql.Connection;
import javax.naming.NamingException;
import javax.sql.DataSource;
/**
* An extended version of JndiObjectFactoryBean that actually tests a JNDI data source before falling back to its
* default object. Allows continued backward compatibility with old-style datasource configuration.
*
* @author dward
*/
public class JndiObjectFactoryBean extends org.springframework.jndi.JndiObjectFactoryBean
{
@Override
protected Object lookup() throws NamingException
{
Object candidate = super.lookup();
if (candidate instanceof DataSource)
{
Connection con = null;
try
{
con = ((DataSource) candidate).getConnection();
}
catch (Exception e)
{
NamingException e1 = new NamingException("Unable to get connection from " + getJndiName());
e1.setRootCause(e);
throw e1;
}
finally
{
try
{
if (con != null)
{
con.close();
}
}
catch (Exception e)
{
}
}
}
return candidate;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.Properties;
import javax.naming.NamingException;
import org.springframework.jndi.JndiTemplate;
/**
* An extended {@link SystemPropertiesFactoryBean} that allows properties to be set through JNDI entries in
* java:comp/env/properties/*. The precedence given to system properties is still as per the superclass.
*
* @author dward
*/
public class JndiPropertiesFactoryBean extends SystemPropertiesFactoryBean
{
private JndiTemplate jndiTemplate = new JndiTemplate();
@Override
protected void resolveMergedProperty(String propertyName, Properties props)
{
try
{
Object value = this.jndiTemplate.lookup("java:comp/env/properties/" + propertyName);
if (value != null)
{
String stringValue = value.toString();
if (stringValue.length() > 0)
{
// Unfortunately, JBoss 4 wrongly expects every env-entry declared in web.xml to have an
// env-entry-value (even though these are meant to be decided on deployment!). So we treat the empty
// string as null.
props.setProperty(propertyName, stringValue);
}
}
}
catch (NamingException e)
{
// Fall back to merged value in props
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.Properties;
import javax.naming.NamingException;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.jndi.JndiTemplate;
/**
* An extended {@link PropertyPlaceholderConfigurer} that allows properties to be set through JNDI entries in
* java:comp/env/properties/*. The precedence given to system properties is still as per the superclass.
*
* @author dward
*/
public class JndiPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer
{
private JndiTemplate jndiTemplate = new JndiTemplate();
@Override
protected String resolvePlaceholder(String placeholder, Properties props)
{
String result = null;
try
{
Object value = this.jndiTemplate.lookup("java:comp/env/properties/" + placeholder);
if (value != null)
{
result = value.toString();
}
}
catch (NamingException e)
{
}
// Unfortunately, JBoss 4 wrongly expects every env-entry declared in web.xml to have an env-entry-value (even
// though these are meant to be decided on deployment!). So we treat the empty string as null.
return result == null || result.length() == 0 ? super.resolvePlaceholder(placeholder, props) : result;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource;
import org.springframework.beans.BeansException;
/**
* A non-blocking version of LazyInitTargetSource.
*
* @author dward
*/
public class NonBlockingLazyInitTargetSource extends AbstractBeanFactoryBasedTargetSource
{
private static final long serialVersionUID = 4509578245779492037L;
private Object target;
private ReadWriteLock lock = new ReentrantReadWriteLock();
public Object getTarget() throws BeansException
{
this.lock.readLock().lock();
try
{
if (this.target != null)
{
return this.target;
}
}
finally
{
this.lock.readLock().unlock();
}
this.lock.writeLock().lock();
try
{
if (this.target == null)
{
this.target = getBeanFactory().getBean(getTargetBeanName());
}
return this.target;
}
finally
{
this.lock.writeLock().unlock();
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.io.IOException;
import java.net.URL;
import java.util.Set;
import org.springframework.core.io.Resource;
import org.springframework.util.PathMatcher;
/**
* An interface for plug ins to JBossEnabledResourcePatternResolver that avoids direct dependencies on
* application server specifics.
*
* @author dward
*/
public interface PathMatchingHelper
{
/**
* Indicates whether this helper is capable of searching the given URL (i.e. its protocol is supported).
*
* @param rootURL
* the root url to be searched
* @return <code>true</code> if this helper is capable of searching the given URL
*/
public boolean canHandle(URL rootURL);
/**
* Gets the resource at the given URL.
*
* @param url URL
* @return the resource at the given URL
* @throws IOException
* for any error
*/
public Resource getResource(URL url) throws IOException;
/**
* Gets the set of resources under the given URL whose path matches the given sub pattern.
*
* @param matcher
* the matcher
* @param rootURL
* the root URL to be searched
* @param subPattern
* the ant-style pattern to match
* @return the set of matching resources
* @throws IOException
* for any error
*/
public Set<Resource> getResources(PathMatcher matcher, URL rootURL, String subPattern) throws IOException;
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.Constants;
/**
* Like the parent <code>PropertiesFactoryBean</code>, but overrides or augments the resulting property set with values
* from VM system properties. As with the Spring {@link PropertyPlaceholderConfigurer} the following modes are
* supported:
* <ul>
* <li><b>SYSTEM_PROPERTIES_MODE_NEVER: </b>Don't use system properties at all.</li>
* <li><b>SYSTEM_PROPERTIES_MODE_FALLBACK: </b>Fallback to a system property only for undefined properties.</li>
* <li><b>SYSTEM_PROPERTIES_MODE_OVERRIDE: (DEFAULT)</b>Use a system property if it is available.</li>
* </ul>
* Note that system properties will only be included in the property set if defaults for the property have already been
* defined using {@link #setProperties(Properties)} or {@link #setLocations(org.springframework.core.io.Resource[])} or
* their names have been included explicitly in the set passed to {@link #setSystemProperties(Set)}.
*
* @author Derek Hulley
*/
public class SystemPropertiesFactoryBean extends PropertiesFactoryBean
{
private static final Constants constants = new Constants(PropertyPlaceholderConfigurer.class);
private int systemPropertiesMode = PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE;
private Set<String> systemProperties = Collections.emptySet();
/**
* Set the system property mode by the name of the corresponding constant, e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
*
* @param constantName
* name of the constant
* @throws java.lang.IllegalArgumentException
* if an invalid constant was specified
* @see #setSystemPropertiesMode
*/
public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException
{
this.systemPropertiesMode = SystemPropertiesFactoryBean.constants.asNumber(constantName).intValue();
}
/**
* Set how to check system properties.
*
* @see PropertyPlaceholderConfigurer#setSystemPropertiesMode(int)
*/
public void setSystemPropertiesMode(int systemPropertiesMode)
{
this.systemPropertiesMode = systemPropertiesMode;
}
/**
* Set the names of the properties that can be considered for overriding.
*
* @param systemProperties
* a set of properties that can be fetched from the system properties
*/
public void setSystemProperties(Set<String> systemProperties)
{
this.systemProperties = systemProperties;
}
@SuppressWarnings("unchecked")
@Override
protected Properties mergeProperties() throws IOException
{
// First do the default merge
Properties props = super.mergeProperties();
// Now resolve all the merged properties
if (this.systemPropertiesMode == PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_NEVER)
{
// If we are in never mode, we don't refer to system properties at all
for (String systemProperty : (Set<String>) (Set) props.keySet())
{
resolveMergedProperty(systemProperty, props);
}
}
else
{
// Otherwise, we allow unset properties to drift through from the systemProperties set and potentially set
// ones to be overriden by system properties
Set<String> propNames = new HashSet<String>((Set<String>) (Set) props.keySet());
propNames.addAll(this.systemProperties);
for (String systemProperty : propNames)
{
resolveMergedProperty(systemProperty, props);
if (this.systemPropertiesMode == PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK
&& props.containsKey(systemProperty))
{
// It's already there
continue;
}
// Get the system value and assign if present
String systemPropertyValue = System.getProperty(systemProperty);
if (systemPropertyValue != null)
{
props.put(systemProperty, systemPropertyValue);
}
}
}
return props;
}
/**
* Override hook. Allows subclasses to resolve a merged property from an alternative source, whilst still respecting
* the chosen system property fallback path.
*
* @param systemProperty String
* @param props Properties
*/
protected void resolveMergedProperty(String systemProperty, Properties props)
{
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Takes a set of properties and pushes them into the Java environment. Usually, VM properties
* are required by the system (see {@link SystemPropertiesFactoryBean} and
* Spring's <tt>PropertyPlaceholderConfigurer</tt>); sometimes it is necessary to take properties
* available to Spring and push them onto the VM.
* <p>
* For simplicity, the system property, if present, will NOT be modified. Also, property placeholders
* (<b>${...}</b>), empty values and null values will be ignored.
* <p>
* Use the {@link #init()} method to push the properties.
*
* @author Derek Hulley
* @since V3.1
*/
public class SystemPropertiesSetterBean
{
private static Log logger = LogFactory.getLog(SystemPropertiesSetterBean.class);
private Map<String, String> propertyMap;
SystemPropertiesSetterBean()
{
propertyMap = new HashMap<String, String>(3);
}
/**
* Set the properties that will be pushed onto the JVM.
*
* @param propertyMap a map of <b>property name</b> to <b>property value</b>
*/
public void setPropertyMap(Map<String, String> propertyMap)
{
this.propertyMap = propertyMap;
}
public void init()
{
for (Map.Entry<String, String> entry : propertyMap.entrySet())
{
String name = entry.getKey();
String value = entry.getValue();
// Some values can be ignored
if (value == null || value.length() == 0)
{
continue;
}
if (value.startsWith("${") && value.endsWith("}"))
{
continue;
}
// Check the system properties
if (System.getProperty(name) != null)
{
// It was already there
if (logger.isDebugEnabled())
{
logger.debug("\n" +
"Not pushing up system property: \n" +
" Property: " + name + "\n" +
" Value already present: " + System.getProperty(name) + "\n" +
" Value provided: " + value);
}
continue;
}
System.setProperty(name, value);
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @since 2.1
* @author Derek Hulley
*/
public abstract class AbstractCharactersetFinder implements CharactersetFinder
{
private static Log logger = LogFactory.getLog(AbstractCharactersetFinder.class);
private static boolean isDebugEnabled = logger.isDebugEnabled();
private int bufferSize;
public AbstractCharactersetFinder()
{
this.bufferSize = 8192;
}
/**
* Set the maximum number of bytes to read ahead when attempting to determine the characterset.
* Most characterset detectors are efficient and can process 8K of buffered data very quickly.
* Some, may need to be constrained a bit.
*
* @param bufferSize the number of bytes - default 8K.
*/
public void setBufferSize(int bufferSize)
{
this.bufferSize = bufferSize;
}
/**
* {@inheritDoc}
* <p>
* The input stream is checked to ensure that it supports marks, after which
* a buffer is extracted, leaving the stream in its original state.
*/
public final Charset detectCharset(InputStream is)
{
// Only support marking streams
if (!is.markSupported())
{
throw new IllegalArgumentException("The InputStream must support marks. Wrap the stream in a BufferedInputStream.");
}
try
{
int bufferSize = getBufferSize();
if (bufferSize < 0)
{
throw new RuntimeException("The required buffer size may not be negative: " + bufferSize);
}
// Mark the stream for just a few more than we actually will need
is.mark(bufferSize);
// Create a buffer to hold the data
byte[] buffer = new byte[bufferSize];
// Fill it
int read = is.read(buffer);
// Create an appropriately sized buffer
if (read > -1 && read < buffer.length)
{
byte[] copyBuffer = new byte[read];
System.arraycopy(buffer, 0, copyBuffer, 0, read);
buffer = copyBuffer;
}
// Detect
return detectCharset(buffer);
}
catch (IOException e)
{
// Attempt a reset
throw new AlfrescoRuntimeException("IOException while attempting to detect charset encoding.", e);
}
finally
{
try { is.reset(); } catch (Throwable ee) {}
}
}
public final Charset detectCharset(byte[] buffer)
{
try
{
Charset charset = detectCharsetImpl(buffer);
// Done
if (isDebugEnabled)
{
if (charset == null)
{
// Read a few characters for debug purposes
logger.debug("\n" +
"Failed to identify stream character set: \n" +
" Guessed 'chars': " + Arrays.toString(buffer));
}
else
{
// Read a few characters for debug purposes
logger.debug("\n" +
"Identified character set from stream:\n" +
" Charset: " + charset + "\n" +
" Detected chars: " + new String(buffer, charset.name()));
}
}
return charset;
}
catch (Throwable e)
{
logger.error("IOException while attempting to detect charset encoding.", e);
return null;
}
}
/**
* Some implementations may only require a few bytes to do detect the stream type,
* whilst others may be more efficient with larger buffers. In either case, the
* number of bytes actually present in the buffer cannot be enforced.
* <p>
* Only override this method if there is a very compelling reason to adjust the buffer
* size, and then consider handling the {@link #setBufferSize(int)} method by issuing a
* warning. This will prevent users from setting the buffer size when it has no effect.
*
* @return Returns the maximum desired size of the buffer passed
* to the {@link CharactersetFinder#detectCharset(byte[])} method.
*
* @see #setBufferSize(int)
*/
protected int getBufferSize()
{
return bufferSize;
}
/**
* Worker method for implementations to override. All exceptions will be reported and
* absorbed and <tt>null</tt> returned.
* <p>
* The interface contract is that the data buffer must not be altered in any way.
*
* @param buffer the buffer of data no bigger than the requested
* {@linkplain #getBufferSize() best buffer size}. This can,
* very efficiently, be turned into an <tt>InputStream</tt> using a
* <tt>ByteArrayInputStream<tt>.
* @return Returns the charset or <tt>null</tt> if an accurate conclusion
* is not possible
* @throws Exception Any exception, checked or not
*/
protected abstract Charset detectCharsetImpl(byte[] buffer) throws Exception;
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Byte Order Marker encoding detection.
*
* @since 2.1
* @author Pacific Northwest National Lab
* @author Derek Hulley
*/
public class BomCharactersetFinder extends AbstractCharactersetFinder
{
private static Log logger = LogFactory.getLog(BomCharactersetFinder.class);
@Override
public void setBufferSize(int bufferSize)
{
logger.warn("Setting the buffersize has no effect for charset finder: " + BomCharactersetFinder.class.getName());
}
/**
* @return Returns 64
*/
@Override
protected int getBufferSize()
{
return 64;
}
/**
* Just searches the Byte Order Marker, i.e. the first three characters for a sign of
* the encoding.
*/
protected Charset detectCharsetImpl(byte[] buffer) throws Exception
{
Charset charset = null;
ByteArrayInputStream bis = null;
try
{
bis = new ByteArrayInputStream(buffer);
bis.mark(3);
char[] byteHeader = new char[3];
InputStreamReader in = new InputStreamReader(bis);
int bytesRead = in.read(byteHeader);
bis.reset();
if (bytesRead < 2)
{
// ASCII
charset = Charset.forName("Cp1252");
}
else if (
byteHeader[0] == 0xFE &&
byteHeader[1] == 0xFF)
{
// UCS-2 Big Endian
charset = Charset.forName("UTF-16BE");
}
else if (
byteHeader[0] == 0xFF &&
byteHeader[1] == 0xFE)
{
// UCS-2 Little Endian
charset = Charset.forName("UTF-16LE");
}
else if (
bytesRead >= 3 &&
byteHeader[0] == 0xEF &&
byteHeader[1] == 0xBB &&
byteHeader[2] == 0xBF)
{
// UTF-8
charset = Charset.forName("UTF-8");
}
else
{
// No idea
charset = null;
}
// Done
return charset;
}
finally
{
if (bis != null)
{
try { bis.close(); } catch (Throwable e) {}
}
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* Interface for classes that are able to read a text-based input stream and determine
* the character encoding.
* <p>
* There are quite a few libraries that do this, but none are perfect. It is therefore
* necessary to abstract the implementation to allow these finders to be configured in
* as required.
* <p>
* Implementations should have a default constructor and be completely thread safe and
* stateless. This will allow them to be constructed and held indefinitely to do the
* decoding work.
* <p>
* Where the encoding cannot be determined, it is left to the client to decide what to do.
* Some implementations may guess and encoding or use a default guess - it is up to the
* implementation to specify the behaviour.
*
* @since 2.1
* @author Derek Hulley
*/
public interface CharactersetFinder
{
/**
* Attempt to detect the character set encoding for the give input stream. The input
* stream will not be altered or closed by this method, and must therefore support
* marking. If the input stream available doesn't support marking, then it can be wrapped with
* a {@link BufferedInputStream}.
* <p>
* The current state of the stream will be restored before the method returns.
*
* @param is an input stream that must support marking
* @return Returns the encoding of the stream,
* or <tt>null</tt> if encoding cannot be identified
*/
public Charset detectCharset(InputStream is);
/**
* Attempt to detect the character set encoding for the given buffer.
*
* @param buffer the first <i>n</i> bytes of the character stream
* @return Returns the encoding of the buffer,
* or <tt>null</tt> if encoding cannot be identified
*/
public Charset detectCharset(byte[] buffer);
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import com.glaforge.i18n.io.CharsetToolkit;
/**
* Uses the <a href="http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding">Guess Encoding</a>
* library.
*
* @since 2.1
* @author Derek Hulley
*/
public class GuessEncodingCharsetFinder extends AbstractCharactersetFinder
{
/** Dummy charset to detect the default guess */
private static final Charset DUMMY_CHARSET = new DummyCharset();
@Override
protected Charset detectCharsetImpl(byte[] buffer) throws Exception
{
CharsetToolkit charsetToolkit = new CharsetToolkit(buffer, DUMMY_CHARSET);
charsetToolkit.setEnforce8Bit(true); // Force the default instead of a guess
Charset charset = charsetToolkit.guessEncoding();
if (charset == DUMMY_CHARSET)
{
return null;
}
else
{
return charset;
}
}
/**
* A dummy charset to detect a default hit.
*/
public static class DummyCharset extends Charset
{
DummyCharset()
{
super("dummy", new String[] {});
}
@Override
public boolean contains(Charset cs)
{
throw new UnsupportedOperationException();
}
@Override
public CharsetDecoder newDecoder()
{
throw new UnsupportedOperationException();
}
@Override
public CharsetEncoder newEncoder()
{
throw new UnsupportedOperationException();
}
}
}

View File

@@ -0,0 +1,312 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SealedObject;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Basic support for encryption engines.
*
* @since 4.0
*/
public abstract class AbstractEncryptor implements Encryptor
{
protected static final Log logger = LogFactory.getLog(Encryptor.class);
protected String cipherAlgorithm;
protected String cipherProvider;
protected KeyProvider keyProvider;
/**
* Constructs with defaults
*/
protected AbstractEncryptor()
{
}
/**
* @param keyProvider provides encryption keys based on aliases
*/
public void setKeyProvider(KeyProvider keyProvider)
{
this.keyProvider = keyProvider;
}
public KeyProvider getKeyProvider()
{
return keyProvider;
}
public void init()
{
PropertyCheck.mandatory(this, "keyProvider", keyProvider);
}
/**
* Factory method to be written by implementations to construct <b>and initialize</b>
* physical ciphering objects.
*
* @param keyAlias the key alias
* @param params algorithm-specific parameters
* @param mode the cipher mode
* @return Cipher
*/
protected abstract Cipher getCipher(String keyAlias, AlgorithmParameters params, int mode);
/**
* {@inheritDoc}
*/
@Override
public Pair<byte[], AlgorithmParameters> encrypt(String keyAlias, AlgorithmParameters params, byte[] input)
{
Cipher cipher = getCipher(keyAlias, params, Cipher.ENCRYPT_MODE);
if (cipher == null)
{
return new Pair<byte[], AlgorithmParameters>(input, null);
}
try
{
byte[] output = cipher.doFinal(input);
params = cipher.getParameters();
return new Pair<byte[], AlgorithmParameters>(output, params);
}
catch (Throwable e)
{
// cipher.init(Cipher.ENCRYPT_MODE, key, params);
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
}
}
protected void resetCipher()
{
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decrypt(String keyAlias, AlgorithmParameters params, byte[] input)
{
Cipher cipher = getCipher(keyAlias, params, Cipher.DECRYPT_MODE);
if (cipher == null)
{
return input;
}
try
{
return cipher.doFinal(input);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
}
}
/**
* {@inheritDoc}
*/
@Override
public InputStream decrypt(String keyAlias, AlgorithmParameters params, InputStream input)
{
Cipher cipher = getCipher(keyAlias, params, Cipher.DECRYPT_MODE);
if (cipher == null)
{
return input;
}
try
{
return new CipherInputStream(input, cipher);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
}
}
/**
* {@inheritDoc}
* <p/>
* Serializes and {@link #encrypt(String, AlgorithmParameters, byte[]) encrypts} the input data.
*/
@Override
public Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias, AlgorithmParameters params, Object input)
{
try
{
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(input);
byte[] unencrypted = bos.toByteArray();
return encrypt(keyAlias, params, unencrypted);
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to serialize or encrypt object", e);
}
}
/**
* {@inheritDoc}
* <p/>
* {@link #decrypt(String, AlgorithmParameters, byte[]) Decrypts} and deserializes the input data
*/
@Override
public Object decryptObject(String keyAlias, AlgorithmParameters params, byte[] input)
{
try
{
byte[] unencrypted = decrypt(keyAlias, params, input);
ByteArrayInputStream bis = new ByteArrayInputStream(unencrypted);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
return obj;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to deserialize or decrypt object", e);
}
}
@Override
public Serializable sealObject(String keyAlias, AlgorithmParameters params, Serializable input)
{
if (input == null)
{
return null;
}
Cipher cipher = getCipher(keyAlias, params, Cipher.ENCRYPT_MODE);
if (cipher == null)
{
return input;
}
try
{
return new SealedObject(input, cipher);
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to seal object", e);
}
}
@Override
public Serializable unsealObject(String keyAlias, Serializable input) throws InvalidKeyException
{
if (input == null)
{
return input;
}
// Don't unseal it if it is not sealed
if (!(input instanceof SealedObject))
{
return input;
}
// Get the Key, rather than a Cipher
Key key = keyProvider.getKey(keyAlias);
if (key == null)
{
// The client will be expecting to unseal the object
throw new IllegalStateException("No key matching " + keyAlias + ". Cannot unseal object.");
}
// Unseal it using the key
SealedObject sealedInput = (SealedObject) input;
try
{
Serializable output = (Serializable) sealedInput.getObject(key);
// Done
return output;
}
catch(InvalidKeyException e)
{
// let these through, can be useful to client code to know this is the cause
throw e;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to unseal object", e);
}
}
public void setCipherAlgorithm(String cipherAlgorithm)
{
this.cipherAlgorithm = cipherAlgorithm;
}
public String getCipherAlgorithm()
{
return this.cipherAlgorithm;
}
public void setCipherProvider(String cipherProvider)
{
this.cipherProvider = cipherProvider;
}
public String getCipherProvider()
{
return this.cipherProvider;
}
/**
* {@inheritDoc}
*/
@Override
public AlgorithmParameters decodeAlgorithmParameters(byte[] encoded)
{
try
{
AlgorithmParameters p = null;
String algorithm = "DESede";
if(getCipherProvider() != null)
{
p = AlgorithmParameters.getInstance(algorithm, getCipherProvider());
}
else
{
p = AlgorithmParameters.getInstance(algorithm);
}
p.init(encoded);
return p;
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("", e);
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
* Basic support for key providers
* <p/>
* TODO: This class will provide the alias name mapping so that use-cases can be mapped
* to different alias names in the keystore.
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractKeyProvider implements KeyProvider
{
/*
* Not a useless class.
*/
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import java.util.Set;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
/**
* Manages a Java Keystore for Alfresco, including caching keys where appropriate.
*
* @since 4.0
*
*/
public interface AlfrescoKeyStore
{
public static final String KEY_KEYSTORE_PASSWORD = "keystore.password";
/**
* The name of the keystore.
*
* @return the name of the keystore.
*/
public String getName();
/**
* Backup the keystore to the backup location. Write the keys to the backup keystore.
*/
public void backup();
/**
* The key store parameters.
*
* @return KeyStoreParameters
*/
public KeyStoreParameters getKeyStoreParameters();
/**
* The backup key store parameters.
*
* @return * @return
*/
public KeyStoreParameters getBackupKeyStoreParameters();
/**
* Does the underlying key store exist?
*
* @return true if it exists, false otherwise
*/
public boolean exists();
/**
* Return the key with the given key alias.
*
* @param keyAlias String
* @return Key
*/
public Key getKey(String keyAlias);
/**
* Return the timestamp (in ms) of when the key was last loaded from the keystore on disk.
*
* @param keyAlias String
* @return long
*/
public long getKeyTimestamp(String keyAlias);
/**
* Return the backup key with the given key alias.
*
* @param keyAlias String
* @return Key
*/
public Key getBackupKey(String keyAlias);
/**
* Return all key aliases in the key store.
*
* @return Set<String>
*/
public Set<String> getKeyAliases();
/**
* Create an array of key managers from keys in the key store.
*
* @return KeyManager[]
*/
public KeyManager[] createKeyManagers();
/**
* Create an array of trust managers from certificates in the key store.
*
* @return TrustManager[]
*/
public TrustManager[] createTrustManagers();
/**
* Create the key store if it doesn't exist.
* A key for each key alias will be written to the keystore on disk, either from the cached keys or, if not present, a key will be generated.
*/
public void create();
/**
* Reload the keys from the key store.
*/
public void reload() throws InvalidKeystoreException, MissingKeyException;
/**
* Check that the keys in the key store are valid i.e. that they match those registered.
*/
public void validateKeys() throws InvalidKeystoreException, MissingKeyException;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
/**
*
* Represents a loaded, cached encryption key. The key can be <tt>null</tt>.
*
* @since 4.0
*
*/
public class CachedKey
{
public static CachedKey NULL = new CachedKey(null, null);
private Key key;
private long timestamp;
CachedKey(Key key, Long timestamp)
{
this.key = key;
this.timestamp = (timestamp != null ? timestamp.longValue() : -1);
}
public CachedKey(Key key)
{
super();
this.key = key;
this.timestamp = System.currentTimeMillis();
}
public Key getKey()
{
return key;
}
public long getTimestamp()
{
return timestamp;
}
}

View File

@@ -0,0 +1,355 @@
/*
* Copyright 2005-2010 Alfresco Software, Ltd. All rights reserved.
*
* License rights for this program may be obtained from Alfresco Software, Ltd.
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.encryption;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* An input stream that encrypts data produced by a {@link EncryptingOutputStream}. A lightweight yet secure hybrid
* encryption scheme is used. A random symmetric key is decrypted using the receiver's private key. The supplied data is
* then decrypted using the symmetric key and read on a streaming basis. When the end of the stream is reached or the
* stream is closed, a HMAC checksum of the entire stream contents is validated.
*/
public class DecryptingInputStream extends InputStream
{
/** The wrapped stream. */
private final DataInputStream wrapped;
/** The input cipher. */
private final Cipher inputCipher;
/** The MAC generator. */
private final Mac mac;
/** Internal buffer for MAC computation. */
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
/** A DataOutputStream on top of our interal buffer. */
private final DataOutputStream dataStr = new DataOutputStream(this.buffer);
/** The current unencrypted data block. */
private byte[] currentDataBlock;
/** The next encrypted data block. (could be the HMAC checksum) */
private byte[] nextDataBlock;
/** Have we read to the end of the underlying stream?. */
private boolean isAtEnd;
/** Our current position within currentDataBlock. */
private int currentDataPos;
/**
* Constructs a DecryptingInputStream using default symmetric encryption parameters.
*
* @param wrapped
* the input stream to decrypt
* @param privKey
* the receiver's private key for decrypting the symmetric key
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws IllegalBlockSizeException
* the illegal block size exception
* @throws BadPaddingException
* the bad padding exception
* @throws InvalidAlgorithmParameterException
* the invalid algorithm parameter exception
* @throws NoSuchProviderException
* the no such provider exception
*/
public DecryptingInputStream(final InputStream wrapped, final PrivateKey privKey) throws IOException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException
{
this(wrapped, privKey, "AES", "CBC", "PKCS5PADDING");
}
/**
* Constructs a DecryptingInputStream.
*
* @param wrapped
* the input stream to decrypt
* @param privKey
* the receiver's private key for decrypting the symmetric key
* @param algorithm
* encryption algorithm (e.g. "AES")
* @param mode
* encryption mode (e.g. "CBC")
* @param padding
* padding scheme (e.g. "PKCS5PADDING")
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws IllegalBlockSizeException
* the illegal block size exception
* @throws BadPaddingException
* the bad padding exception
* @throws InvalidAlgorithmParameterException
* the invalid algorithm parameter exception
* @throws NoSuchProviderException
* the no such provider exception
*/
public DecryptingInputStream(final InputStream wrapped, final PrivateKey privKey, final String algorithm,
final String mode, final String padding) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException, NoSuchProviderException
{
// Initialise a secure source of randomness
this.wrapped = new DataInputStream(wrapped);
final SecureRandom secRand = SecureRandom.getInstance("SHA1PRNG");
// Set up RSA
final Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING");
rsa.init(Cipher.DECRYPT_MODE, privKey, secRand);
// Read and decrypt the symmetric key
final SecretKey symKey = new SecretKeySpec(rsa.doFinal(readBlock()), algorithm);
// Read and decrypt initialisation vector
final byte[] keyIV = rsa.doFinal(readBlock());
// Set up cipher for decryption
this.inputCipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding);
this.inputCipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(keyIV));
// Read and decrypt the MAC key
final SecretKey macKey = new SecretKeySpec(this.inputCipher.doFinal(readBlock()), "HMACSHA1");
// Set up HMAC
this.mac = Mac.getInstance("HMACSHA1");
this.mac.init(macKey);
// Always read a block ahead so we can intercept the HMAC block
this.nextDataBlock = readBlock(false);
}
/**
* Reads the next block of data, adding it to the HMAC checksum. Strips the header recording the number of bytes in
* the block.
*
* @return the data block, or <code>null</code> if the end of the stream has been reached
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private byte[] readBlock() throws IOException
{
return readBlock(true);
}
/**
* Reads the next block of data, optionally adding it to the HMAC checksum. Strips the header recording the number
* of bytes in the block.
*
* @param updateMac
* should the block be added to the HMAC checksum?
* @return the data block, or <code>null</code> if the end of the stream has been reached
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private byte[] readBlock(final boolean updateMac) throws IOException
{
int len;
try
{
len = this.wrapped.readInt();
}
catch (final EOFException e)
{
return null;
}
final byte[] in = new byte[len];
this.wrapped.readFully(in);
if (updateMac)
{
macBlock(in);
}
return in;
}
/**
* Updates the HMAC checksum with the given data block.
*
* @param block
* the block
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private void macBlock(final byte[] block) throws IOException
{
this.dataStr.writeInt(block.length);
this.dataStr.write(block);
// If we don't have the MAC key yet, buffer up until we do
if (this.mac != null)
{
this.dataStr.flush();
final byte[] bytes = this.buffer.toByteArray();
this.buffer.reset();
this.mac.update(bytes);
}
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read()
*/
@Override
public int read() throws IOException
{
final byte[] buf = new byte[1];
int bytesRead;
while ((bytesRead = read(buf)) == 0)
{
;
}
return bytesRead == -1 ? -1 : buf[0] & 0xFF;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read(byte[])
*/
@Override
public int read(final byte b[]) throws IOException
{
return read(b, 0, b.length);
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(final byte b[], int off, final int len) throws IOException
{
if (b == null)
{
throw new NullPointerException();
}
else if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0)
{
throw new IndexOutOfBoundsException();
}
else if (len == 0)
{
return 0;
}
int bytesToRead = len;
OUTER: while (bytesToRead > 0)
{
// Fetch another block if necessary
while (this.currentDataBlock == null || this.currentDataPos >= this.currentDataBlock.length)
{
byte[] newDataBlock;
// We're right at the end of the last block so finish
if (this.isAtEnd)
{
this.currentDataBlock = this.nextDataBlock = null;
break OUTER;
}
// We've already read the last block so validate the MAC code
else if ((newDataBlock = readBlock(false)) == null)
{
if (!MessageDigest.isEqual(this.mac.doFinal(), this.nextDataBlock))
{
throw new IOException("Invalid HMAC");
}
// We still have what's left in the cipher to read
try
{
this.currentDataBlock = this.inputCipher.doFinal();
}
catch (final GeneralSecurityException e)
{
throw new RuntimeException(e);
}
this.isAtEnd = true;
}
// We have an ordinary data block to MAC and decrypt
else
{
macBlock(this.nextDataBlock);
this.currentDataBlock = this.inputCipher.update(this.nextDataBlock);
this.nextDataBlock = newDataBlock;
}
this.currentDataPos = 0;
}
final int bytesRead = Math.min(bytesToRead, this.currentDataBlock.length - this.currentDataPos);
System.arraycopy(this.currentDataBlock, this.currentDataPos, b, off, bytesRead);
bytesToRead -= bytesRead;
off += bytesRead;
this.currentDataPos += bytesRead;
}
return bytesToRead == len ? -1 : len - bytesToRead;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#available()
*/
@Override
public int available() throws IOException
{
return this.currentDataBlock == null ? 0 : this.currentDataBlock.length - this.currentDataPos;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#close()
*/
@Override
public void close() throws IOException
{
// Read right to the end, just to ensure the MAC code is valid!
if (this.nextDataBlock != null)
{
final byte[] skipBuff = new byte[1024];
while (read(skipBuff) != -1)
{
;
}
}
this.wrapped.close();
this.dataStr.close();
}
}

View File

@@ -0,0 +1,480 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.AlgorithmParameters;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.encryption.MACUtils.MACInput;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.IPUtils;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.Base64;
import org.springframework.util.FileCopyUtils;
/**
* Various encryption utility methods.
*
* @since 4.0
*/
public class DefaultEncryptionUtils implements EncryptionUtils
{
// Logger
protected static Log logger = LogFactory.getLog(Encryptor.class);
protected static String HEADER_ALGORITHM_PARAMETERS = "XAlfresco-algorithmParameters";
protected static String HEADER_MAC = "XAlfresco-mac";
protected static String HEADER_TIMESTAMP = "XAlfresco-timestamp";
protected Encryptor encryptor;
protected MACUtils macUtils;
protected long messageTimeout; // ms
protected String remoteIP;
protected String localIP;
public DefaultEncryptionUtils()
{
try
{
this.localIP = InetAddress.getLocalHost().getHostAddress();
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("Unable to initialise EncryptionUtils", e);
}
}
public String getRemoteIP()
{
return remoteIP;
}
public void setRemoteIP(String remoteIP)
{
try
{
this.remoteIP = IPUtils.getRealIPAddress(remoteIP);
}
catch (UnknownHostException e)
{
throw new AlfrescoRuntimeException("Failed to get server IP address", e);
}
}
/**
* Get the local registered IP address for authentication purposes
*
* @return String
*/
protected String getLocalIPAddress()
{
return localIP;
}
public void setMessageTimeout(long messageTimeout)
{
this.messageTimeout = messageTimeout;
}
public void setEncryptor(Encryptor encryptor)
{
this.encryptor = encryptor;
}
public void setMacUtils(MACUtils macUtils)
{
this.macUtils = macUtils;
}
protected void setRequestMac(HttpMethod method, byte[] mac)
{
if(mac == null)
{
throw new AlfrescoRuntimeException("Mac cannot be null");
}
method.setRequestHeader(HEADER_MAC, Base64.encodeBytes(mac));
}
/**
* Set the MAC on the HTTP response
*
* @param response HttpServletResponse
* @param mac byte[]
*/
protected void setMac(HttpServletResponse response, byte[] mac)
{
if(mac == null)
{
throw new AlfrescoRuntimeException("Mac cannot be null");
}
response.setHeader(HEADER_MAC, Base64.encodeBytes(mac));
}
/**
* Get the MAC (Message Authentication Code) on the HTTP request
*
* @param req HttpServletRequest
* @return the MAC
* @throws IOException
*/
protected byte[] getMac(HttpServletRequest req) throws IOException
{
String header = req.getHeader(HEADER_MAC);
if(header != null)
{
return Base64.decode(header);
}
else
{
return null;
}
}
/**
* Get the MAC (Message Authentication Code) on the HTTP response
*
* @param res HttpMethod
* @return the MAC
* @throws IOException
*/
protected byte[] getResponseMac(HttpMethod res) throws IOException
{
Header header = res.getResponseHeader(HEADER_MAC);
if(header != null)
{
return Base64.decode(header.getValue());
}
else
{
return null;
}
}
/**
* Set the timestamp on the HTTP request
* @param method HttpMethod
* @param timestamp (ms, in UNIX time)
*/
protected void setRequestTimestamp(HttpMethod method, long timestamp)
{
method.setRequestHeader(HEADER_TIMESTAMP, String.valueOf(timestamp));
}
/**
* Set the timestamp on the HTTP response
* @param res HttpServletResponse
* @param timestamp (ms, in UNIX time)
*/
protected void setTimestamp(HttpServletResponse res, long timestamp)
{
res.setHeader(HEADER_TIMESTAMP, String.valueOf(timestamp));
}
/**
* Get the timestamp on the HTTP response
*
* @param method HttpMethod
* @return timestamp (ms, in UNIX time)
* @throws IOException
*/
protected Long getResponseTimestamp(HttpMethod method) throws IOException
{
Header header = method.getResponseHeader(HEADER_TIMESTAMP);
if(header != null)
{
return Long.valueOf(header.getValue());
}
else
{
return null;
}
}
/**
* Get the timestamp on the HTTP request
*
* @param method HttpServletRequest
* @return timestamp (ms, in UNIX time)
* @throws IOException
*/
protected Long getTimestamp(HttpServletRequest method) throws IOException
{
String header = method.getHeader(HEADER_TIMESTAMP);
if(header != null)
{
return Long.valueOf(header);
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public void setRequestAlgorithmParameters(HttpMethod method, AlgorithmParameters params) throws IOException
{
if(params != null)
{
method.setRequestHeader(HEADER_ALGORITHM_PARAMETERS, Base64.encodeBytes(params.getEncoded()));
}
}
/**
* Set the algorithm parameters header on the HTTP response
*
* @param response HttpServletResponse
* @param params AlgorithmParameters
* @throws IOException
*/
protected void setAlgorithmParameters(HttpServletResponse response, AlgorithmParameters params) throws IOException
{
if(params != null)
{
response.setHeader(HEADER_ALGORITHM_PARAMETERS, Base64.encodeBytes(params.getEncoded()));
}
}
/**
* Decode cipher algorithm parameters from the HTTP method
*
* @param method HttpMethod
* @return decoded algorithm parameters
* @throws IOException
*/
protected AlgorithmParameters decodeAlgorithmParameters(HttpMethod method) throws IOException
{
Header header = method.getResponseHeader(HEADER_ALGORITHM_PARAMETERS);
if(header != null)
{
byte[] algorithmParams = Base64.decode(header.getValue());
AlgorithmParameters algorithmParameters = encryptor.decodeAlgorithmParameters(algorithmParams);
return algorithmParameters;
}
else
{
return null;
}
}
/**
* Decode cipher algorithm parameters from the HTTP method
*
* @param req
* @return decoded algorithm parameters
* @throws IOException
*/
protected AlgorithmParameters decodeAlgorithmParameters(HttpServletRequest req) throws IOException
{
String header = req.getHeader(HEADER_ALGORITHM_PARAMETERS);
if(header != null)
{
byte[] algorithmParams = Base64.decode(header);
AlgorithmParameters algorithmParameters = encryptor.decodeAlgorithmParameters(algorithmParams);
return algorithmParameters;
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decryptResponseBody(HttpMethod method) throws IOException
{
// TODO fileoutputstream if content is especially large?
InputStream body = method.getResponseBodyAsStream();
if(body != null)
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
FileCopyUtils.copy(body, out);
AlgorithmParameters params = decodeAlgorithmParameters(method);
if(params != null)
{
byte[] decrypted = encryptor.decrypt(KeyProvider.ALIAS_SOLR, params, out.toByteArray());
return decrypted;
}
else
{
throw new AlfrescoRuntimeException("Unable to decrypt response body, missing encryption algorithm parameters");
}
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decryptBody(HttpServletRequest req) throws IOException
{
if(req.getMethod().equals("POST"))
{
InputStream bodyStream = req.getInputStream();
if(bodyStream != null)
{
// expect algorithParameters header
AlgorithmParameters p = decodeAlgorithmParameters(req);
// decrypt the body
InputStream in = encryptor.decrypt(KeyProvider.ALIAS_SOLR, p, bodyStream);
return IOUtils.toByteArray(in);
}
else
{
return null;
}
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean authenticateResponse(HttpMethod method, String remoteIP, byte[] decryptedBody)
{
try
{
byte[] expectedMAC = getResponseMac(method);
Long timestamp = getResponseTimestamp(method);
if(timestamp == null)
{
return false;
}
remoteIP = IPUtils.getRealIPAddress(remoteIP);
return authenticate(expectedMAC, new MACInput(decryptedBody, timestamp.longValue(), remoteIP));
}
catch(Exception e)
{
throw new RuntimeException("Unable to authenticate HTTP response", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean authenticate(HttpServletRequest req, byte[] decryptedBody)
{
try
{
byte[] expectedMAC = getMac(req);
Long timestamp = getTimestamp(req);
if(timestamp == null)
{
return false;
}
String ipAddress = IPUtils.getRealIPAddress(req.getRemoteAddr());
return authenticate(expectedMAC, new MACInput(decryptedBody, timestamp.longValue(), ipAddress));
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("Unable to authenticate HTTP request", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void setRequestAuthentication(HttpMethod method, byte[] message) throws IOException
{
long requestTimestamp = System.currentTimeMillis();
// add MAC header
byte[] mac = macUtils.generateMAC(KeyProvider.ALIAS_SOLR, new MACInput(message, requestTimestamp, getLocalIPAddress()));
if(logger.isDebugEnabled())
{
logger.debug("Setting MAC " + Arrays.toString(mac) + " on HTTP request " + method.getPath());
logger.debug("Setting timestamp " + requestTimestamp + " on HTTP request " + method.getPath());
}
setRequestMac(method, mac);
// prevent replays
setRequestTimestamp(method, requestTimestamp);
}
/**
* {@inheritDoc}
*/
@Override
public void setResponseAuthentication(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
byte[] responseBody, AlgorithmParameters params) throws IOException
{
long responseTimestamp = System.currentTimeMillis();
byte[] mac = macUtils.generateMAC(KeyProvider.ALIAS_SOLR,
new MACInput(responseBody, responseTimestamp, getLocalIPAddress()));
if(logger.isDebugEnabled())
{
logger.debug("Setting MAC " + Arrays.toString(mac) + " on HTTP response to request " + httpRequest.getRequestURI());
logger.debug("Setting timestamp " + responseTimestamp + " on HTTP response to request " + httpRequest.getRequestURI());
}
setAlgorithmParameters(httpResponse, params);
setMac(httpResponse, mac);
// prevent replays
setTimestamp(httpResponse, responseTimestamp);
}
protected boolean authenticate(byte[] expectedMAC, MACInput macInput)
{
// check the MAC and, if valid, check that the timestamp is under the threshold and that the remote IP is
// the expected IP
boolean authorized = macUtils.validateMAC(KeyProvider.ALIAS_SOLR, expectedMAC, macInput) &&
validateTimestamp(macInput.getTimestamp());
return authorized;
}
protected boolean validateTimestamp(long timestamp)
{
long currentTime = System.currentTimeMillis();
return((currentTime - timestamp) < messageTimeout);
}
}

View File

@@ -0,0 +1,268 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.PropertyCheck;
/**
* @author Derek Hulley
* @since 4.0
*/
public class DefaultEncryptor extends AbstractEncryptor
{
private boolean cacheCiphers = true;
private final ThreadLocal<Map<CipherKey, CachedCipher>> threadCipher;
/**
* Default constructor for IOC
*/
public DefaultEncryptor()
{
threadCipher = new ThreadLocal<Map<CipherKey, CachedCipher>>();
}
/**
* Convenience constructor for tests
*/
/* package */ DefaultEncryptor(KeyProvider keyProvider, String cipherAlgorithm, String cipherProvider)
{
this();
setKeyProvider(keyProvider);
setCipherAlgorithm(cipherAlgorithm);
setCipherProvider(cipherProvider);
}
public void init()
{
super.init();
PropertyCheck.mandatory(this, "cipherAlgorithm", cipherAlgorithm);
}
public void setCacheCiphers(boolean cacheCiphers)
{
this.cacheCiphers = cacheCiphers;
}
protected Cipher createCipher(int mode, String algorithm, String provider, Key key, AlgorithmParameters params)
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException
{
Cipher cipher = null;
if (cipherProvider == null)
{
cipher = Cipher.getInstance(algorithm);
}
else
{
cipher = Cipher.getInstance(algorithm, provider);
}
cipher.init(mode, key, params);
return cipher;
}
protected Cipher getCachedCipher(String keyAlias, int mode, AlgorithmParameters params, Key key)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidAlgorithmParameterException
{
CachedCipher cipherInfo = null;
Cipher cipher = null;
Map<CipherKey, CachedCipher> ciphers = threadCipher.get();
if(ciphers == null)
{
ciphers = new HashMap<CipherKey, CachedCipher>(5);
threadCipher.set(ciphers);
}
cipherInfo = ciphers.get(new CipherKey(keyAlias, mode));
if(cipherInfo == null)
{
cipher = createCipher(mode, cipherAlgorithm, cipherProvider, key, params);
ciphers.put(new CipherKey(keyAlias, mode), new CachedCipher(cipher, key));
// Done
if (logger.isDebugEnabled())
{
logger.debug("Cipher constructed: alias=" + keyAlias + "; mode=" + mode + ": " + cipher);
}
}
else
{
// the key has changed, re-construct the cipher
if(cipherInfo.getKey() != key)
{
// key has changed, rendering the cached cipher out of date. Re-create the cipher with
// the new key.
cipher = createCipher(mode, cipherAlgorithm, cipherProvider, key, params);
ciphers.put(new CipherKey(keyAlias, mode), new CachedCipher(cipher, key));
}
else
{
cipher = cipherInfo.getCipher();
}
}
return cipher;
}
@Override
public Cipher getCipher(String keyAlias, AlgorithmParameters params, int mode)
{
Cipher cipher = null;
// Get the encryption key
Key key = keyProvider.getKey(keyAlias);
if(key == null)
{
// No encryption possible
return null;
}
try
{
if(cacheCiphers)
{
cipher = getCachedCipher(keyAlias, mode, params, key);
}
else
{
cipher = createCipher(mode, cipherAlgorithm, cipherProvider, key, params);
}
}
catch (Exception e)
{
throw new AlfrescoRuntimeException(
"Failed to construct cipher: alias=" + keyAlias + "; mode=" + mode,
e);
}
return cipher;
}
public boolean keyAvailable(String keyAlias)
{
return keyProvider.getKey(keyAlias) != null;
}
private static class CipherKey
{
private String keyAlias;
private int mode;
public CipherKey(String keyAlias, int mode)
{
super();
this.keyAlias = keyAlias;
this.mode = mode;
}
public String getKeyAlias()
{
return keyAlias;
}
public int getMode()
{
return mode;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result
+ ((keyAlias == null) ? 0 : keyAlias.hashCode());
result = prime * result + mode;
return result;
}
@Override
public boolean equals(Object obj)
{
if(this == obj)
{
return true;
}
if(!(obj instanceof CipherKey))
{
return false;
}
CipherKey other = (CipherKey)obj;
if(keyAlias == null)
{
if (other.keyAlias != null)
{
return false;
}
}
else if(!keyAlias.equals(other.keyAlias))
{
return false;
}
if(mode != other.mode)
{
return false;
}
return true;
}
}
/*
* Stores a cipher and the key used to construct it.
*/
private static class CachedCipher
{
private Key key;
private Cipher cipher;
public CachedCipher(Cipher cipher, Key key)
{
super();
this.cipher = cipher;
this.key = key;
}
public Cipher getCipher()
{
return cipher;
}
public Key getKey()
{
return key;
}
}
}

View File

@@ -0,0 +1,230 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.InputStream;
import java.io.Serializable;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.Pair;
/**
* The fallback encryptor provides a fallback mechanism for decryption, first using the default
* encryption keys and, if they fail (perhaps because they have been changed), falling back
* to a backup set of keys.
*
* Note that encryption will be performed only using the default encryption keys.
*
* @since 4.0
*/
public class DefaultFallbackEncryptor implements FallbackEncryptor
{
private Encryptor fallback;
private Encryptor main;
public DefaultFallbackEncryptor()
{
}
public DefaultFallbackEncryptor(Encryptor main, Encryptor fallback)
{
this();
this.main = main;
this.fallback = fallback;
}
public void setFallback(Encryptor fallback)
{
this.fallback = fallback;
}
public void setMain(Encryptor main)
{
this.main = main;
}
/**
* {@inheritDoc}
*/
@Override
public Pair<byte[], AlgorithmParameters> encrypt(String keyAlias,
AlgorithmParameters params, byte[] input)
{
// Note: encrypt supported only for main encryptor
Pair<byte[], AlgorithmParameters> ret = main.encrypt(keyAlias, params, input);
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decrypt(String keyAlias, AlgorithmParameters params,
byte[] input)
{
byte[] ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.decrypt(keyAlias, params, input);
}
catch(Throwable e)
{
ret = fallback.decrypt(keyAlias, params, input);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public InputStream decrypt(String keyAlias, AlgorithmParameters params,
InputStream in)
{
InputStream ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.decrypt(keyAlias, params, in);
}
catch(Throwable e)
{
ret = fallback.decrypt(keyAlias, params, in);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias,
AlgorithmParameters params, Object input)
{
// Note: encrypt supported only for main encryptor
Pair<byte[], AlgorithmParameters> ret = main.encryptObject(keyAlias, params, input);
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Object decryptObject(String keyAlias, AlgorithmParameters params,
byte[] input)
{
Object ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.decryptObject(keyAlias, params, input);
}
catch(Throwable e)
{
ret = fallback.decryptObject(keyAlias, params, input);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Serializable sealObject(String keyAlias, AlgorithmParameters params,
Serializable input)
{
// Note: encrypt supported only for main encryptor
Serializable ret = main.sealObject(keyAlias, params, input);
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Serializable unsealObject(String keyAlias, Serializable input)
throws InvalidKeyException
{
Serializable ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.unsealObject(keyAlias, input);
}
catch(Throwable e)
{
ret = fallback.unsealObject(keyAlias, input);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public AlgorithmParameters decodeAlgorithmParameters(byte[] encoded)
{
AlgorithmParameters ret;
try
{
ret = main.decodeAlgorithmParameters(encoded);
}
catch(AlfrescoRuntimeException e)
{
ret = fallback.decodeAlgorithmParameters(encoded);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public boolean keyAvailable(String keyAlias)
{
return main.keyAvailable(keyAlias);
}
/**
* {@inheritDoc}
*/
@Override
public boolean backupKeyAvailable(String keyAlias)
{
return fallback.keyAvailable(keyAlias);
}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2005-2010 Alfresco Software, Ltd. All rights reserved.
*
* License rights for this program may be obtained from Alfresco Software, Ltd.
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.encryption;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
/**
* An output stream that encrypts data to another output stream. A lightweight yet secure hybrid encryption scheme is
* used. A random symmetric key is generated and encrypted using the receiver's public key. The supplied data is then
* encrypted using the symmetric key and sent to the underlying stream on a streaming basis. An HMAC checksum is also
* computed on an ongoing basis and appended to the output when the stream is closed. This class can be used in
* conjunction with {@link DecryptingInputStream} to transport data securely.
*/
public class EncryptingOutputStream extends OutputStream
{
/** The wrapped stream. */
private final OutputStream wrapped;
/** The output cipher. */
private final Cipher outputCipher;
/** The MAC generator. */
private final Mac mac;
/** Internal buffer for MAC computation. */
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
/** A DataOutputStream on top of our interal buffer. */
private final DataOutputStream dataStr = new DataOutputStream(this.buffer);
/**
* Constructs an EncryptingOutputStream using default symmetric encryption parameters.
*
* @param wrapped
* outputstream to store the encrypted data
* @param receiverKey
* the receiver's public key for encrypting the symmetric key
* @param rand
* a secure source of randomness
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws BadPaddingException
* the bad padding exception
* @throws IllegalBlockSizeException
* the illegal block size exception
*/
public EncryptingOutputStream(final OutputStream wrapped, final PublicKey receiverKey, final SecureRandom rand)
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException
{
this(wrapped, receiverKey, "AES", rand, 128, "CBC", "PKCS5PADDING");
}
/**
* Constructs an EncryptingOutputStream.
*
* @param wrapped
* outputstream to store the encrypted data
* @param receiverKey
* the receiver's public key for encrypting the symmetric key
* @param algorithm
* symmetric encryption algorithm (e.g. "AES")
* @param rand
* a secure source of randomness
* @param strength
* the key size in bits (e.g. 128)
* @param mode
* encryption mode (e.g. "CBC")
* @param padding
* padding scheme (e.g. "PKCS5PADDING")
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws BadPaddingException
* the bad padding exception
* @throws IllegalBlockSizeException
* the illegal block size exception
*/
public EncryptingOutputStream(final OutputStream wrapped, final PublicKey receiverKey, final String algorithm,
final SecureRandom rand, final int strength, final String mode, final String padding) throws IOException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// Initialise
this.wrapped = wrapped;
// Generate a random symmetric key
final KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
keyGen.init(strength, rand);
final Key symKey = keyGen.generateKey();
// Instantiate Symmetric cipher for encryption.
this.outputCipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding);
this.outputCipher.init(Cipher.ENCRYPT_MODE, symKey, rand);
// Set up HMAC
this.mac = Mac.getInstance("HMACSHA1");
final byte[] macKeyBytes = new byte[20];
rand.nextBytes(macKeyBytes);
final Key macKey = new SecretKeySpec(macKeyBytes, "HMACSHA1");
this.mac.init(macKey);
// Set up RSA to encrypt symmetric key
final Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING");
rsa.init(Cipher.ENCRYPT_MODE, receiverKey, rand);
// Write the header
// Write out an RSA-encrypted block for the key of the cipher.
writeBlock(rsa.doFinal(symKey.getEncoded()));
// Write out RSA-encrypted Initialisation Vector block
writeBlock(rsa.doFinal(this.outputCipher.getIV()));
// Write out key for HMAC.
writeBlock(this.outputCipher.doFinal(macKey.getEncoded()));
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#write(int)
*/
@Override
public void write(final int b) throws IOException
{
write(new byte[]
{
(byte) b
}, 0, 1);
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#write(byte[])
*/
@Override
public void write(final byte b[]) throws IOException
{
write(b, 0, b.length);
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#write(byte[], int, int)
*/
@Override
public void write(final byte b[], final int off, final int len) throws IOException
{
if (b == null)
{
throw new NullPointerException();
}
else if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0)
{
throw new IndexOutOfBoundsException();
}
else if (len == 0)
{
return;
}
final byte[] out = this.outputCipher.update(b, off, len); // Encrypt data.
if (out != null && out.length > 0)
{
writeBlock(out);
}
}
/**
* Writes a block of data, preceded by its length, and adds it to the HMAC checksum.
*
* @param out
* the data to be written.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private void writeBlock(final byte[] out) throws IOException
{
this.dataStr.writeInt(out.length); // Write length.
this.dataStr.write(out); // Write encrypted data.
this.dataStr.flush();
final byte[] block = this.buffer.toByteArray();
this.buffer.reset();
this.mac.update(block);
this.wrapped.write(block);
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#flush()
*/
@Override
public void flush() throws IOException
{
this.wrapped.flush();
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#close()
*/
@Override
public void close() throws IOException
{
try
{
// Write the last block
writeBlock(this.outputCipher.doFinal());
}
catch (final GeneralSecurityException e)
{
throw new RuntimeException(e);
}
// Write the MAC code
writeBlock(this.mac.doFinal());
this.wrapped.close();
this.dataStr.close();
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import java.util.List;
import java.util.Set;
/**
* Stores registered encryption keys.
*
* @since 4.0
*
*/
public interface EncryptionKeysRegistry
{
public static enum KEY_STATUS
{
OK, CHANGED, MISSING;
};
/**
* Is the key with alias 'keyAlias' registered?
* @param keyAlias String
* @return boolean
*/
public boolean isKeyRegistered(String keyAlias);
/**
* Register the key.
*
* @param keyAlias String
* @param key Key
*/
public void registerKey(String keyAlias, Key key);
/**
* Unregister the key.
*
* @param keyAlias String
*/
public void unregisterKey(String keyAlias);
/**
* Check the validity of the key against the registry.
*
* @param keyAlias String
* @param key Key
* @return KEY_STATUS
*/
public KEY_STATUS checkKey(String keyAlias, Key key);
/**
* Remove the set of keys from the registry.
*
* @param keys Set<String>
*/
public void removeRegisteredKeys(Set<String> keys);
/**
* Return those keys in the set that have been registered.
*
* @param keys Set<String>
* @return List<String>
*/
public List<String> getRegisteredKeys(Set<String> keys);
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.IOException;
import java.security.AlgorithmParameters;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpMethod;
/**
* Various encryption utility methods.
*
* @since 4.0
*/
public interface EncryptionUtils
{
/**
* Decrypt the response body of the http method
*
* @param method
* @return decrypted response body
* @throws IOException
*/
public byte[] decryptResponseBody(HttpMethod method) throws IOException;
/**
* Decrypt the body of the http request
*
* @param req
* @return decrypted response body
* @throws IOException
*/
public byte[] decryptBody(HttpServletRequest req) throws IOException;
/**
* Authenticate the http method response: validate the MAC, check that the remote IP is
* as expected and that the timestamp is recent.
*
* @param method
* @param remoteIP
* @param decryptedBody
* @return true if the method reponse is authentic, false otherwise
*/
public boolean authenticateResponse(HttpMethod method, String remoteIP, byte[] decryptedBody);
/**
* Authenticate the http request: validate the MAC, check that the remote IP is
* as expected and that the timestamp is recent.
*
* @param req
* @param decryptedBody
* @return true if the method request is authentic, false otherwise
*/
public boolean authenticate(HttpServletRequest req, byte[] decryptedBody);
/**
* Encrypt the http method request body
*
* @param method
* @param message
* @throws IOException
*/
public void setRequestAuthentication(HttpMethod method, byte[] message) throws IOException;
/**
* Sets authentication headers on the HTTP response.
*
* @param httpRequest
* @param httpResponse
* @param responseBody
* @param params
* @throws IOException
*/
public void setResponseAuthentication(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
byte[] responseBody, AlgorithmParameters params) throws IOException;
/**
* Set the algorithm parameters header on the method request
*
* @param method
* @param params
* @throws IOException
*/
public void setRequestAlgorithmParameters(HttpMethod method, AlgorithmParameters params) throws IOException;
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.InputStream;
import java.io.Serializable;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import org.alfresco.util.Pair;
/**
* Interface providing methods to encrypt and decrypt data.
*
* @since 4.0
*/
public interface Encryptor
{
/**
* Encrypt some bytes
*
* @param keyAlias the encryption key alias
* @param input the data to encrypt
* @return the encrypted data and parameters used
*/
Pair<byte[], AlgorithmParameters> encrypt(String keyAlias, AlgorithmParameters params, byte[] input);
/**
* Decrypt some bytes
*
* @param keyAlias the encryption key alias
* @param input the data to decrypt
* @return the unencrypted data
*/
byte[] decrypt(String keyAlias, AlgorithmParameters params, byte[] input);
/**
* Decrypt an input stream
*
* @param keyAlias the encryption key alias
* @param in the data to decrypt
* @return the unencrypted data
*/
InputStream decrypt(String keyAlias, AlgorithmParameters params, InputStream in);
/**
* Encrypt an object
*
* @param keyAlias the encryption key alias
* @param input the object to write to bytes
* @return the encrypted data and parameters used
*/
Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias, AlgorithmParameters params, Object input);
/**
* Decrypt data as an object
*
* @param keyAlias the encryption key alias
* @param input the data to decrypt
* @return the unencrypted data deserialized
*/
Object decryptObject(String keyAlias, AlgorithmParameters params, byte[] input);
/**
* Convenience method to seal on object up cryptographically.
* <p/>
* Note that the original object may be returned directly if there is no key associated with
* the alias.
*
* @param keyAlias the encryption key alias
* @param input the object to encrypt and seal
* @return the sealed object that can be decrypted with the original key
*/
Serializable sealObject(String keyAlias, AlgorithmParameters params, Serializable input);
/**
* Convenience method to unseal on object sealed up cryptographically.
* <p/>
* Note that the algorithm parameters not provided on the assumption that a symmetric key
* algorithm is in use - only the key is required for unsealing.
* <p/>
* Note that the original object may be returned directly if there is no key associated with
* the alias or if the input object is not a <code>SealedObject</code>.
*
* @param keyAlias the encryption key alias
* @param input the object to decrypt and unseal
* @return the original unsealed object that was encrypted with the original key
* @throws IllegalStateException if the key alias is not valid <b>and</b> the input is a
* <tt>SealedObject</tt>
*/
Serializable unsealObject(String keyAlias, Serializable input) throws InvalidKeyException;
/**
* Decodes encoded cipher algorithm parameters
*
* @param encoded the encoded cipher algorithm parameters
* @return the decoded cipher algorithmParameters
*/
AlgorithmParameters decodeAlgorithmParameters(byte[] encoded);
boolean keyAvailable(String keyAlias);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
* A fallback encryptor provides a fallback mechanism for decryption, first using the default
* encryption keys and, if they fail (perhaps because they have been changed), falling back
* to a backup set of keys.
*
* Note that encryption will be performed only using the default encryption keys.
*
* @since 4.0
*/
public interface FallbackEncryptor extends Encryptor
{
/**
* Is the backup key available in order to fall back to?
*
* @return boolean
*/
boolean backupKeyAvailable(String keyAlias);
}

View File

@@ -0,0 +1,47 @@
package org.alfresco.encryption;
import java.security.SecureRandom;
import javax.crypto.spec.DESedeKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
*
* Generate a secret key for use by the repository.
*
* @since 4.0
*
*/
public class GenerateSecretKey
{
public byte[] generateKeyData()
{
try
{
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(System.currentTimeMillis());
byte bytes[] = new byte[DESedeKeySpec.DES_EDE_KEY_LEN];
random.nextBytes(bytes);
return bytes;
}
catch(Exception e)
{
throw new RuntimeException("Unable to generate secret key", e);
}
}
public static void main(String args[])
{
try
{
GenerateSecretKey gen = new GenerateSecretKey();
byte[] bytes = gen.generateKeyData();
System.out.print(Base64.encodeBase64String(bytes));
}
catch(Throwable e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
*
* @since 4.0
*
*/
public class InvalidKeystoreException extends Exception
{
private static final long serialVersionUID = -1324791685965572313L;
public InvalidKeystoreException(String message)
{
super(message);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* A simple map of key aliases to keys. Each key has an associated timestamp indicating
* when it was last loaded from the keystore on disk.
*
* @since 4.0
*
*/
public class KeyMap
{
private Map<String, CachedKey> keys;
public KeyMap()
{
this.keys = new HashMap<String, CachedKey>(5);
}
public KeyMap(Map<String, CachedKey> keys)
{
super();
this.keys = keys;
}
public int numKeys()
{
return keys.size();
}
public Set<String> getKeyAliases()
{
return keys.keySet();
}
// always returns an instance; if null will return a CachedKey.NULL
public CachedKey getCachedKey(String keyAlias)
{
CachedKey cachedKey = keys.get(keyAlias);
return (cachedKey != null ? cachedKey : CachedKey.NULL);
}
public Key getKey(String keyAlias)
{
return getCachedKey(keyAlias).getKey();
}
public void setKey(String keyAlias, Key key)
{
keys.put(keyAlias, new CachedKey(key));
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
/**
* A key provider returns the secret keys for different use cases.
*
* @since 4.0
*/
public interface KeyProvider
{
// TODO: Allow the aliases to be configured i.e. include an alias mapper
/**
* Constant representing the keystore alias for keys to encrypt/decrypt node metadata
*/
public static final String ALIAS_METADATA = "metadata";
/**
* Constant representing the keystore alias for keys to encrypt/decrypt SOLR transfer data
*/
public static final String ALIAS_SOLR = "solr";
/**
* Get an encryption key if available.
*
* @param keyAlias the key alias
* @return the encryption key and a timestamp of when it was last changed
*/
public Key getKey(String keyAlias);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Manages key resources (key store and key store passwords)
*
* @since 4.0
*
*/
public interface KeyResourceLoader
{
/**
* Loads and returns an InputStream of the key store at the configured location.
* If the file cannot be found this method returns null.
*
* @return InputStream
* @throws FileNotFoundException
*/
public InputStream getKeyStore(String keyStoreLocation) throws FileNotFoundException;
/**
* Loads key metadata from the configured passwords file location.
*
* Note that the passwords are not cached locally.
* If the file cannot be found this method returns null.
*
* @return Properties
* @throws IOException
*/
public Properties loadKeyMetaData(String keyMetaDataFileLocation) throws IOException, FileNotFoundException;
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import org.alfresco.util.PropertyCheck;
/**
* Stores Java keystore initialisation parameters.
*
* @since 4.0
*
*/
public class KeyStoreParameters
{
private String name;
private String type;
private String provider;
private String keyMetaDataFileLocation;
private String location;
public KeyStoreParameters()
{
}
public KeyStoreParameters(String name, String type, String keyStoreProvider,
String keyMetaDataFileLocation, String location)
{
super();
this.name = name;
this.type = type;
this.provider = keyStoreProvider;
this.keyMetaDataFileLocation = keyMetaDataFileLocation;
this.location = location;
}
public void init()
{
if (!PropertyCheck.isValidPropertyString(getLocation()))
{
setLocation(null);
}
if (!PropertyCheck.isValidPropertyString(getProvider()))
{
setProvider(null);
}
if (!PropertyCheck.isValidPropertyString(getType()))
{
setType(null);
}
if (!PropertyCheck.isValidPropertyString(getKeyMetaDataFileLocation()))
{
setKeyMetaDataFileLocation(null);
}
}
public String getName()
{
return name;
}
public String getType()
{
return type;
}
public String getProvider()
{
return provider;
}
public String getKeyMetaDataFileLocation()
{
return keyMetaDataFileLocation;
}
public String getLocation()
{
return location;
}
public void setName(String name)
{
this.name = name;
}
public void setType(String type)
{
this.type = type;
}
public void setProvider(String provider)
{
this.provider = provider;
}
public void setKeyMetaDataFileLocation(String keyMetaDataFileLocation)
{
this.keyMetaDataFileLocation = keyMetaDataFileLocation;
}
public void setLocation(String location)
{
this.location = location;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.util.List;
/**
* A report on which keys have changed and which keys have not changed.
*
* @since 4.0
*
*/
public class KeysReport
{
private List<String> keysChanged;
private List<String> keysUnchanged;
public KeysReport(List<String> keysChanged, List<String> keysUnchanged)
{
super();
this.keysChanged = keysChanged;
this.keysUnchanged = keysUnchanged;
}
public List<String> getKeysChanged()
{
return keysChanged;
}
public List<String> getKeysUnchanged()
{
return keysUnchanged;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
*
* Provides system-wide secret keys for symmetric database encryption from a key store
* in the filesystem. Just wraps a key store.
*
* @author Derek Hulley
* @since 4.0
*/
public class KeystoreKeyProvider extends AbstractKeyProvider
{
private static final Log logger = LogFactory.getLog(KeystoreKeyProvider.class);
private AlfrescoKeyStore keyStore;
private boolean useBackupKeys = false;
/**
* Constructs the provider with required defaults
*/
public KeystoreKeyProvider()
{
}
public KeystoreKeyProvider(KeyStoreParameters keyStoreParameters, KeyResourceLoader keyResourceLoader)
{
this();
this.keyStore = new AlfrescoKeyStoreImpl(keyStoreParameters, keyResourceLoader);
init();
}
public void setUseBackupKeys(boolean useBackupKeys)
{
this.useBackupKeys = useBackupKeys;
}
/**
*
* @param keyStore
*/
public KeystoreKeyProvider(AlfrescoKeyStore keyStore)
{
this();
this.keyStore = keyStore;
init();
}
public void setKeyStore(AlfrescoKeyStore keyStore)
{
this.keyStore = keyStore;
}
public void init()
{
}
/**
* {@inheritDoc}
*/
@Override
public Key getKey(String keyAlias)
{
if(useBackupKeys)
{
return keyStore.getBackupKey(keyAlias);
}
else
{
return keyStore.getKey(keyAlias);
}
}
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Key;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.crypto.Mac;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Provides support for generating and checking MACs (Message Authentication Codes) using Alfresco's
* secret keys.
*
* @since 4.0
*
*/
public class MACUtils
{
private static Log logger = LogFactory.getLog(Encryptor.class);
private static byte SEPARATOR = 0;
private final ThreadLocal<Mac> threadMac;
private KeyProvider keyProvider;
private String macAlgorithm;
/**
* Default constructor for IOC
*/
public MACUtils()
{
threadMac = new ThreadLocal<Mac>();
}
public void setKeyProvider(KeyProvider keyProvider)
{
this.keyProvider = keyProvider;
}
public void setMacAlgorithm(String macAlgorithm)
{
this.macAlgorithm = macAlgorithm;
}
protected Mac getMac(String keyAlias) throws Exception
{
Mac mac = threadMac.get();
if(mac == null)
{
mac = Mac.getInstance(macAlgorithm);
threadMac.set(mac);
}
Key key = keyProvider.getKey(keyAlias);
if(key == null)
{
throw new AlfrescoRuntimeException("Unexpected null key for key alias " + keyAlias);
}
mac.init(key);
return mac;
}
protected byte[] longToByteArray(long l) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeLong(l);
dos.flush();
return bos.toByteArray();
}
public byte[] generateMAC(String keyAlias, MACInput macInput)
{
try
{
InputStream fullMessage = macInput.getMACInput();
if(logger.isDebugEnabled())
{
logger.debug("Generating MAC for " + macInput + "...");
}
Mac mac = getMac(keyAlias);
byte[] buf = new byte[1024];
int len;
while((len = fullMessage.read(buf, 0, 1024)) != -1)
{
mac.update(buf, 0, len);
}
byte[] newMAC = mac.doFinal();
if(logger.isDebugEnabled())
{
logger.debug("...done. MAC is " + Arrays.toString(newMAC));
}
return newMAC;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to generate MAC", e);
}
}
/**
* Compares the expectedMAC against the MAC generated from
* Assumes message has been decrypted
* @param keyAlias String
* @param expectedMAC byte[]
* @param macInput MACInput
* @return boolean
*/
public boolean validateMAC(String keyAlias, byte[] expectedMAC, MACInput macInput)
{
try
{
byte[] mac = generateMAC(keyAlias, macInput);
if(logger.isDebugEnabled())
{
logger.debug("Validating expected MAC " + Arrays.toString(expectedMAC) + " against mac " + Arrays.toString(mac) + " for MAC input " + macInput + "...");
}
boolean areEqual = Arrays.equals(expectedMAC, mac);
if(logger.isDebugEnabled())
{
logger.debug(areEqual ? "...MAC validation succeeded." : "...MAC validation failed.");
}
return areEqual;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to validate MAC", e);
}
}
/**
* Represents the information to be fed into the MAC generator
*
* @since 4.0
*
*/
public static class MACInput
{
// The message, may be null
private InputStream message;
private long timestamp;
private String ipAddress;
public MACInput(byte[] message, long timestamp, String ipAddress)
{
this.message = (message != null ? new ByteArrayInputStream(message) : null);
this.timestamp = timestamp;
this.ipAddress = ipAddress;
}
public InputStream getMessage()
{
return message;
}
public long getTimestamp()
{
return timestamp;
}
public String getIpAddress()
{
return ipAddress;
}
public InputStream getMACInput() throws IOException
{
List<InputStream> inputStreams = new ArrayList<InputStream>();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bytes);
out.writeUTF(ipAddress);
out.writeByte(SEPARATOR);
out.writeLong(timestamp);
inputStreams.add(new ByteArrayInputStream(bytes.toByteArray()));
if(message != null)
{
inputStreams.add(message);
}
return new MessageInputStream(inputStreams);
}
public String toString()
{
StringBuilder sb = new StringBuilder("MACInput[");
sb.append("timestamp: ").append(getTimestamp());
sb.append("ipAddress: ").append(getIpAddress());
return sb.toString();
}
}
private static class MessageInputStream extends InputStream
{
private List<InputStream> input;
private InputStream activeInputStream;
private int currentStream = 0;
public MessageInputStream(List<InputStream> input)
{
this.input = input;
this.currentStream = 0;
this.activeInputStream = input.get(currentStream);
}
@Override
public void close() throws IOException
{
IOException firstIOException = null;
for(InputStream in : input)
{
try
{
in.close();
}
catch(IOException e)
{
if(firstIOException == null)
{
firstIOException = e;
}
}
}
if(firstIOException != null)
{
throw firstIOException;
}
}
@Override
public int read() throws IOException
{
int i = activeInputStream.read();
if(i == -1)
{
currentStream++;
if(currentStream >= input.size())
{
return -1;
}
else
{
activeInputStream = input.get(currentStream);
i = activeInputStream.read();
}
}
return i;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
*
* @since 4.0
*
*/
public class MissingKeyException extends Exception
{
private static final long serialVersionUID = -7843412242954504581L;
private String keyAlias;
private String keyStoreLocation;
public MissingKeyException(String message)
{
super(message);
}
public MissingKeyException(String keyAlias, String keyStoreLocation)
{
// TODO i18n
super("Key " + keyAlias + " is missing from keystore " + keyStoreLocation);
}
public String getKeyAlias()
{
return keyAlias;
}
public String getKeyStoreLocation()
{
return keyStoreLocation;
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.util.ResourceUtils;
/**
* Loads key resources (key store and key store passwords) from the Spring classpath.
*
* @since 4.0
*
*/
public class SpringKeyResourceLoader implements KeyResourceLoader, ApplicationContextAware
{
/**
* The application context might not be available, in which case the usual URL
* loading is used.
*/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
/**
* Helper method to switch between application context resource loading or
* simpler current classloader resource loading.
*/
private InputStream getSafeInputStream(String location)
{
try
{
final InputStream is;
if (applicationContext != null)
{
Resource resource = applicationContext.getResource(location);
if (resource.exists())
{
is = new BufferedInputStream(resource.getInputStream());
}
else
{
// Fall back to conventional loading
File file = ResourceUtils.getFile(location);
if (file.exists())
{
is = new BufferedInputStream(new FileInputStream(file));
}
else
{
is = null;
}
}
}
else
{
// Load conventionally (i.e. we are in a unit test)
File file = ResourceUtils.getFile(location);
if (file.exists())
{
is = new BufferedInputStream(new FileInputStream(file));
}
else
{
is = null;
}
}
return is;
}
catch (IOException e)
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public InputStream getKeyStore(String keyStoreLocation)
{
if (keyStoreLocation == null)
{
return null;
}
return getSafeInputStream(keyStoreLocation);
}
/**
* {@inheritDoc}
*/
@Override
public Properties loadKeyMetaData(String keyMetaDataFileLocation) throws IOException
{
if (keyMetaDataFileLocation == null)
{
return null;
}
try
{
InputStream is = getSafeInputStream(keyMetaDataFileLocation);
if (is == null)
{
return null;
}
else
{
try
{
Properties p = new Properties();
p.load(is);
return p;
}
finally
{
try { is.close(); } catch (Throwable e) {}
}
}
}
catch(FileNotFoundException e)
{
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption.ssl;
/**
* <p>
* Signals fatal error in initialization of {@link AuthSSLProtocolSocketFactory}.
* </p>
*
* <p>
* Adapted from code here: http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLX509TrustManager.java?revision=608014&view=co
* </p>
*
* @since 4.0
*/
public class AuthSSLInitializationError extends Error
{
private static final long serialVersionUID = 8135341334029823112L;
/**
* Creates a new AuthSSLInitializationError.
*/
public AuthSSLInitializationError()
{
super();
}
/**
* Creates a new AuthSSLInitializationError with the specified message.
*
* @param message error message
*/
public AuthSSLInitializationError(String message)
{
super(message);
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption.ssl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.KeyStore;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import org.alfresco.encryption.AlfrescoKeyStore;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
* Mutual Authentication against an Alfresco repository.
*
* AuthSSLProtocolSocketFactory can be used to validate the identity of the HTTPS
* server against a list of trusted certificates and to authenticate to the HTTPS
* server using a private key.
* </p>
*
* <p>
* Adapted from code here: http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLX509TrustManager.java?revision=608014&view=co
* </p>
*
* <p>
* AuthSSLProtocolSocketFactory will enable server authentication when supplied with
* a {@link KeyStore truststore} file containing one or several trusted certificates.
* The client secure socket will reject the connection during the SSL session handshake
* if the target HTTPS server attempts to authenticate itself with a non-trusted
* certificate.
* </p>
*
* <p>
* AuthSSLProtocolSocketFactory will enable client authentication when supplied with
* a {@link KeyStore keystore} file containg a private key/public certificate pair.
* The client secure socket will use the private key to authenticate itself to the target
* HTTPS server during the SSL session handshake if requested to do so by the server.
* The target HTTPS server will in its turn verify the certificate presented by the client
* in order to establish client's authenticity
* </p>
*
*
* @since 4.0
*/
public class AuthSSLProtocolSocketFactory implements SecureProtocolSocketFactory
{
/** Log object for this class. */
private static final Log logger = LogFactory.getLog(AuthSSLProtocolSocketFactory.class);
private SSLContext sslcontext = null;
private AlfrescoKeyStore keyStore = null;
private AlfrescoKeyStore trustStore = null;
/**
* Constructor for AuthSSLProtocolSocketFactory. Either a keystore or truststore file
* must be given. Otherwise SSL context initialization error will result.
*
* @param sslKeyStore SSL parameters to use.
* @param keyResourceLoader loads key resources from an arbitrary source e.g. classpath
*/
public AuthSSLProtocolSocketFactory(AlfrescoKeyStore sslKeyStore, AlfrescoKeyStore sslTrustStore, KeyResourceLoader keyResourceLoader)
{
super();
this.keyStore = sslKeyStore;
this.trustStore = sslTrustStore;
}
private SSLContext createSSLContext()
{
KeyManager[] keymanagers = keyStore.createKeyManagers();;
TrustManager[] trustmanagers = trustStore.createTrustManagers();
try
{
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(keymanagers, trustmanagers, null);
return sslcontext;
}
catch(Throwable e)
{
throw new AlfrescoRuntimeException("Unable to create SSL context", e);
}
}
private SSLContext getSSLContext()
{
try
{
if(this.sslcontext == null)
{
this.sslcontext = createSSLContext();
}
return this.sslcontext;
}
catch(Throwable e)
{
throw new AlfrescoRuntimeException("Unable to create SSL context", e);
}
}
/**
* Attempts to get a new socket connection to the given host within the given time limit.
* <p>
* To circumvent the limitations of older JREs that do not support connect timeout a
* controller thread is executed. The controller thread attempts to create a new socket
* within the given limit of time. If socket constructor does not return until the
* timeout expires, the controller terminates and throws an {@link ConnectTimeoutException}
* </p>
*
* @param host the host name/IP
* @param port the port on the host
* @param localAddress the local host name/IP to bind the socket to
* @param localPort the port on the local machine
* @param params {@link HttpConnectionParams Http connection parameters}
*
* @return Socket a new socket
*
* @throws IOException if an I/O error occurs while creating the socket
* @throws UnknownHostException if the IP address of the host cannot be
* determined
*/
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort,
final HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException
{
SSLSocket sslSocket = null;
if(params == null)
{
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketfactory = getSSLContext().getSocketFactory();
if(timeout == 0)
{
sslSocket = (SSLSocket)socketfactory.createSocket(host, port, localAddress, localPort);
}
else
{
sslSocket = (SSLSocket)socketfactory.createSocket();
SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteaddr = new InetSocketAddress(host, port);
sslSocket.bind(localaddr);
sslSocket.connect(remoteaddr, timeout);
}
return sslSocket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
*/
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort)
throws IOException, UnknownHostException
{
SSLSocket sslSocket = (SSLSocket)getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
return sslSocket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
*/
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
SSLSocket sslSocket = (SSLSocket)getSSLContext().getSocketFactory().createSocket(host, port);
return sslSocket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
*/
public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
throws IOException, UnknownHostException
{
SSLSocket sslSocket = (SSLSocket)getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
return sslSocket;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption.ssl;
import org.alfresco.encryption.KeyStoreParameters;
/**
*
* @since 4.0
*
*/
public class SSLEncryptionParameters
{
private KeyStoreParameters keyStoreParameters;
private KeyStoreParameters trustStoreParameters;
/**
* Default constructor (for use by Spring)
*/
public SSLEncryptionParameters()
{
super();
}
public SSLEncryptionParameters(KeyStoreParameters keyStoreParameters, KeyStoreParameters trustStoreParameters)
{
super();
this.keyStoreParameters = keyStoreParameters;
this.trustStoreParameters = trustStoreParameters;
}
public KeyStoreParameters getKeyStoreParameters()
{
return keyStoreParameters;
}
public KeyStoreParameters getTrustStoreParameters()
{
return trustStoreParameters;
}
public void setKeyStoreParameters(KeyStoreParameters keyStoreParameters)
{
this.keyStoreParameters = keyStoreParameters;
}
public void setTrustStoreParameters(KeyStoreParameters trustStoreParameters)
{
this.trustStoreParameters = trustStoreParameters;
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.error;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.extensions.surf.util.I18NUtil;
import org.alfresco.api.AlfrescoPublicApi;
/**
* I18n'ed runtime exception thrown by Alfresco code.
*
* @author gavinc
*/
@AlfrescoPublicApi
public class AlfrescoRuntimeException extends RuntimeException
{
/**
* Serial version UUID
*/
private static final long serialVersionUID = 3787143176461219632L;
private static final String MESSAGE_DELIMITER = " ";
private String msgId;
private transient Object[] msgParams = null;
/**
* Helper factory method making use of variable argument numbers
*/
public static AlfrescoRuntimeException create(String msgId, Object ...objects)
{
return new AlfrescoRuntimeException(msgId, objects);
}
/**
* Helper factory method making use of variable argument numbers
*/
public static AlfrescoRuntimeException create(Throwable cause, String msgId, Object ...objects)
{
return new AlfrescoRuntimeException(msgId, objects, cause);
}
/**
* Utility to convert a general Throwable to a RuntimeException. No conversion is done if the
* throwable is already a <tt>RuntimeException</tt>.
*
* @see #create(Throwable, String, Object...)
*/
public static RuntimeException makeRuntimeException(Throwable e, String msgId, Object ...objects)
{
if (e instanceof RuntimeException)
{
return (RuntimeException) e;
}
// Convert it
return AlfrescoRuntimeException.create(e, msgId, objects);
}
/**
* Constructor
*
* @param msgId the message id
*/
public AlfrescoRuntimeException(String msgId)
{
super(resolveMessage(msgId, null));
this.msgId = msgId;
}
/**
* Constructor
*
* @param msgId the message id
* @param msgParams the message parameters
*/
public AlfrescoRuntimeException(String msgId, Object[] msgParams)
{
super(resolveMessage(msgId, msgParams));
this.msgId = msgId;
this.msgParams = msgParams;
}
/**
* Constructor
*
* @param msgId the message id
* @param cause the exception cause
*/
public AlfrescoRuntimeException(String msgId, Throwable cause)
{
super(resolveMessage(msgId, null), cause);
this.msgId = msgId;
}
/**
* Constructor
*
* @param msgId the message id
* @param msgParams the message parameters
* @param cause the exception cause
*/
public AlfrescoRuntimeException(String msgId, Object[] msgParams, Throwable cause)
{
super(resolveMessage(msgId, msgParams), cause);
this.msgId = msgId;
this.msgParams = msgParams;
}
/**
* @return the msgId
*/
public String getMsgId()
{
return msgId;
}
/**
* @return the msgParams
*/
public Object[] getMsgParams()
{
return msgParams;
}
/**
* @return the numericalId
*/
public String getNumericalId()
{
return getMessage().split(MESSAGE_DELIMITER)[0];
}
/**
* Resolves the message id to the localised string.
* <p>
* If a localised message can not be found then the message Id is
* returned.
*
* @param messageId the message Id
* @param params message parameters
* @return the localised message (or the message id if none found)
*/
private static String resolveMessage(String messageId, Object[] params)
{
String message = I18NUtil.getMessage(messageId, params);
if (message == null)
{
// If a localized string cannot be found then return the messageId and the params
message = messageId;
if (params != null)
{
message += " - " + Arrays.toString(params);
}
}
return buildErrorLogNumber(message);
}
/**
* Generate an error log number - based on MMDDXXXX - where M is month,
* D is day and X is an atomic integer count.
*
* @param message Message to prepend the error log number to
*
* @return message with error log number prefix
*/
private static String buildErrorLogNumber(String message)
{
// ensure message is not null
if (message == null)
{
message= "";
}
Date today = new Date();
StringBuilder buf = new StringBuilder(message.length() + 10);
padInt(buf, today.getMonth(), 2);
padInt(buf, today.getDate(), 2);
padInt(buf, errorCounter.getAndIncrement(), 4);
buf.append(MESSAGE_DELIMITER);
buf.append(message);
return buf.toString();
}
/**
* Helper to zero pad a number to specified length
*/
private static void padInt(StringBuilder buffer, int value, int length)
{
String strValue = Integer.toString(value);
for (int i = length - strValue.length(); i > 0; i--)
{
buffer.append('0');
}
buffer.append(strValue);
}
private static AtomicInteger errorCounter = new AtomicInteger();
/**
* Get the root cause.
*/
public Throwable getRootCause()
{
Throwable cause = this;
for (Throwable tmp = this; tmp != null ; tmp = cause.getCause())
{
cause = tmp;
}
return cause;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.error;
/**
* Helper class to provide information about exception stacks.
*
* @author Derek Hulley
*/
public class ExceptionStackUtil
{
/**
* Searches through the exception stack of the given throwable to find any instance
* of the possible cause. The top-level throwable will also be tested.
*
* @param throwable the exception condition to search
* @param possibleCauses the types of the exception conditions of interest
* @return Returns the first instance that matches one of the given
* possible types, or null if there is nothing in the stack
*/
public static Throwable getCause(Throwable throwable, Class<?> ... possibleCauses)
{
while (throwable != null)
{
for (Class<?> possibleCauseClass : possibleCauses)
{
Class<?> throwableClass = throwable.getClass();
if (possibleCauseClass.isAssignableFrom(throwableClass))
{
// We have a match
return throwable;
}
}
// There was no match, so dig deeper
Throwable cause = throwable.getCause();
throwable = (throwable == cause) ? null : cause;
}
// Nothing found
return null;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.error;
/**
* Helper class around outputting stack traces.
*
* @author Derek Hulley
*/
public class StackTraceUtil
{
/**
* Builds a message with the stack trace of the form:
* <pre>
* SOME MESSAGE:
* Started at:
* com.package...
* com.package...
* ...
* </pre>
*
* @param msg the initial error message
* @param stackTraceElements the stack trace elements
* @param sb the buffer to append to
* @param maxDepth the maximum number of trace elements to output. 0 or less means output all.
*/
public static void buildStackTrace(
String msg,
StackTraceElement[] stackTraceElements,
StringBuilder sb,
int maxDepth)
{
String lineEnding = System.getProperty("line.separator", "\n");
sb.append(msg).append(" ").append(lineEnding)
.append(" Started at: ").append(lineEnding);
for (int i = 0; i < stackTraceElements.length; i++)
{
if (i > maxDepth && maxDepth > 0)
{
sb.append(" ...");
break;
}
sb.append(" ").append(stackTraceElements[i]);
if (i < stackTraceElements.length - 1)
{
sb.append(lineEnding);
}
}
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.IOException;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public abstract class AbstractHttpClient implements AlfrescoHttpClient
{
private static final Log logger = LogFactory.getLog(AlfrescoHttpClient.class);
public static final String ALFRESCO_DEFAULT_BASE_URL = "/alfresco";
public static final int DEFAULT_SAVEPOST_BUFFER = 4096;
// Remote Server access
protected HttpClient httpClient = null;
private String baseUrl = ALFRESCO_DEFAULT_BASE_URL;
public AbstractHttpClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
protected HttpClient getHttpClient()
{
return httpClient;
}
/**
* @return the baseUrl
*/
public String getBaseUrl()
{
return baseUrl;
}
/**
* @param baseUrl the baseUrl to set
*/
public void setBaseUrl(String baseUrl)
{
this.baseUrl = baseUrl;
}
private boolean isRedirect(HttpMethod method)
{
switch (method.getStatusCode()) {
case HttpStatus.SC_MOVED_TEMPORARILY:
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_SEE_OTHER:
case HttpStatus.SC_TEMPORARY_REDIRECT:
if (method.getFollowRedirects()) {
return true;
} else {
return false;
}
default:
return false;
}
}
/**
* Send Request to the repository
*/
protected HttpMethod sendRemoteRequest(Request req) throws AuthenticationException, IOException
{
if (logger.isDebugEnabled())
{
logger.debug("");
logger.debug("* Request: " + req.getMethod() + " " + req.getFullUri() + (req.getBody() == null ? "" : "\n" + new String(req.getBody(), "UTF-8")));
}
HttpMethod method = createMethod(req);
// execute method
executeMethod(method);
// Deal with redirect
if(isRedirect(method))
{
Header locationHeader = method.getResponseHeader("location");
if (locationHeader != null)
{
String redirectLocation = locationHeader.getValue();
method.setURI(new URI(redirectLocation, true));
httpClient.executeMethod(method);
}
}
return method;
}
protected long executeMethod(HttpMethod method) throws HttpException, IOException
{
// execute method
long startTime = System.currentTimeMillis();
// TODO: Pool, and sent host configuration and state on execution
getHttpClient().executeMethod(method);
return System.currentTimeMillis() - startTime;
}
protected HttpMethod createMethod(Request req) throws IOException
{
StringBuilder url = new StringBuilder(128);
url.append(baseUrl);
url.append("/service/");
url.append(req.getFullUri());
// construct method
HttpMethod httpMethod = null;
String method = req.getMethod();
if(method.equalsIgnoreCase("GET"))
{
GetMethod get = new GetMethod(url.toString());
httpMethod = get;
httpMethod.setFollowRedirects(true);
}
else if(method.equalsIgnoreCase("POST"))
{
PostMethod post = new PostMethod(url.toString());
httpMethod = post;
ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(req.getBody(), req.getType());
if (req.getBody().length > DEFAULT_SAVEPOST_BUFFER)
{
post.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, true);
}
post.setRequestEntity(requestEntity);
// Note: not able to automatically follow redirects for POST, this is handled by sendRemoteRequest
}
else if(method.equalsIgnoreCase("HEAD"))
{
HeadMethod head = new HeadMethod(url.toString());
httpMethod = head;
httpMethod.setFollowRedirects(true);
}
else
{
throw new AlfrescoRuntimeException("Http Method " + method + " not supported");
}
if (req.getHeaders() != null)
{
for (Map.Entry<String, String> header : req.getHeaders().entrySet())
{
httpMethod.setRequestHeader(header.getKey(), header.getValue());
}
}
return httpMethod;
}
/* (non-Javadoc)
* @see org.alfresco.httpclient.AlfrescoHttpClient#close()
*/
@Override
public void close()
{
if(httpClient != null)
{
HttpConnectionManager connectionManager = httpClient.getHttpConnectionManager();
if(connectionManager instanceof MultiThreadedHttpConnectionManager)
{
((MultiThreadedHttpConnectionManager)connectionManager).shutdown();
}
}
}
}

View File

@@ -0,0 +1,30 @@
package org.alfresco.httpclient;
import java.io.IOException;
/**
*
* @since 4.0
*
*/
public interface AlfrescoHttpClient
{
/**
* Send Request to the repository
*/
public Response sendRequest(Request req) throws AuthenticationException, IOException;
/**
* Set the base url to alfresco
* - normally /alfresco
* @param baseUrl
*/
public void setBaseUrl(String baseUrl);
/**
*
*/
public void close();
}

View File

@@ -0,0 +1,21 @@
package org.alfresco.httpclient;
import org.apache.commons.httpclient.HttpMethod;
public class AuthenticationException extends Exception
{
private static final long serialVersionUID = -407003742855571557L;
private HttpMethod method;
public AuthenticationException(HttpMethod method)
{
this.method = method;
}
public HttpMethod getMethod()
{
return method;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
/**
* HTTP GET Request
*
* @since 4.0
*/
public class GetRequest extends Request
{
public GetRequest(String uri)
{
super("get", uri);
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
/**
* HTTP HEAD request
*
* @since 4.0
*/
public class HeadRequest extends Request
{
public HeadRequest(String uri)
{
super("head", uri);
}
}

View File

@@ -0,0 +1,791 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AlgorithmParameters;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.alfresco.encryption.AlfrescoKeyStore;
import org.alfresco.encryption.AlfrescoKeyStoreImpl;
import org.alfresco.encryption.EncryptionUtils;
import org.alfresco.encryption.Encryptor;
import org.alfresco.encryption.KeyProvider;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.encryption.KeyStoreParameters;
import org.alfresco.encryption.ssl.AuthSSLProtocolSocketFactory;
import org.alfresco.encryption.ssl.SSLEncryptionParameters;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.Pair;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpHost;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.HttpVersion;
import org.apache.commons.httpclient.HttpsURL;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.SimpleHttpConnectionManager;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.DefaultHttpParams;
import org.apache.commons.httpclient.params.DefaultHttpParamsFactory;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.params.HttpParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A factory to create HttpClients and AlfrescoHttpClients based on the setting of the 'secureCommsType' property.
*
* @since 4.0
*/
public class HttpClientFactory
{
public static enum SecureCommsType
{
HTTPS, NONE;
public static SecureCommsType getType(String type)
{
if(type.equalsIgnoreCase("https"))
{
return HTTPS;
}
else if(type.equalsIgnoreCase("none"))
{
return NONE;
}
else
{
throw new IllegalArgumentException("Invalid communications type");
}
}
};
private static final Log logger = LogFactory.getLog(HttpClientFactory.class);
private SSLEncryptionParameters sslEncryptionParameters;
private KeyResourceLoader keyResourceLoader;
private SecureCommsType secureCommsType;
// for md5 http client (no longer used but kept for now)
private KeyStoreParameters keyStoreParameters;
private MD5EncryptionParameters encryptionParameters;
private String host;
private int port;
private int sslPort;
private AlfrescoKeyStore sslKeyStore;
private AlfrescoKeyStore sslTrustStore;
private ProtocolSocketFactory sslSocketFactory;
private int maxTotalConnections = 40;
private int maxHostConnections = 40;
private Integer socketTimeout = null;
private int connectionTimeout = 0;
public HttpClientFactory()
{
}
public HttpClientFactory(SecureCommsType secureCommsType, SSLEncryptionParameters sslEncryptionParameters,
KeyResourceLoader keyResourceLoader, KeyStoreParameters keyStoreParameters,
MD5EncryptionParameters encryptionParameters, String host, int port, int sslPort, int maxTotalConnections,
int maxHostConnections, int socketTimeout)
{
this.secureCommsType = secureCommsType;
this.sslEncryptionParameters = sslEncryptionParameters;
this.keyResourceLoader = keyResourceLoader;
this.keyStoreParameters = keyStoreParameters;
this.encryptionParameters = encryptionParameters;
this.host = host;
this.port = port;
this.sslPort = sslPort;
this.maxTotalConnections = maxTotalConnections;
this.maxHostConnections = maxHostConnections;
this.socketTimeout = socketTimeout;
init();
}
public void init()
{
this.sslKeyStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getKeyStoreParameters(), keyResourceLoader);
this.sslTrustStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getTrustStoreParameters(), keyResourceLoader);
this.sslSocketFactory = new AuthSSLProtocolSocketFactory(sslKeyStore, sslTrustStore, keyResourceLoader);
// Setup the Apache httpclient library to use our concurrent HttpParams factory
DefaultHttpParams.setHttpParamsFactory(new NonBlockingHttpParamsFactory());
}
public void setHost(String host)
{
this.host = host;
}
public String getHost()
{
return host;
}
public void setPort(int port)
{
this.port = port;
}
public int getPort()
{
return port;
}
public void setSslPort(int sslPort)
{
this.sslPort = sslPort;
}
public boolean isSSL()
{
return secureCommsType == SecureCommsType.HTTPS;
}
public void setSecureCommsType(String type)
{
try
{
this.secureCommsType = SecureCommsType.getType(type);
}
catch(IllegalArgumentException e)
{
throw new AlfrescoRuntimeException("", e);
}
}
public void setSSLEncryptionParameters(SSLEncryptionParameters sslEncryptionParameters)
{
this.sslEncryptionParameters = sslEncryptionParameters;
}
public void setKeyStoreParameters(KeyStoreParameters keyStoreParameters)
{
this.keyStoreParameters = keyStoreParameters;
}
public void setEncryptionParameters(MD5EncryptionParameters encryptionParameters)
{
this.encryptionParameters = encryptionParameters;
}
public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader)
{
this.keyResourceLoader = keyResourceLoader;
}
/**
* @return the maxTotalConnections
*/
public int getMaxTotalConnections()
{
return maxTotalConnections;
}
/**
* @param maxTotalConnections the maxTotalConnections to set
*/
public void setMaxTotalConnections(int maxTotalConnections)
{
this.maxTotalConnections = maxTotalConnections;
}
/**
* @return the maxHostConnections
*/
public int getMaxHostConnections()
{
return maxHostConnections;
}
/**
* @param maxHostConnections the maxHostConnections to set
*/
public void setMaxHostConnections(int maxHostConnections)
{
this.maxHostConnections = maxHostConnections;
}
/**
* Sets the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the
* timeout for waiting for data. A timeout value of zero is interpreted as an infinite
* timeout.
*
* @param socketTimeout Timeout in milliseconds
*/
public void setSocketTimeout(Integer socketTimeout)
{
this.socketTimeout = socketTimeout;
}
/**
* Attempts to connect to a server will timeout after this period (millis).
* Default is zero (the timeout is not used).
*
* @param connectionTimeout time in millis.
*/
public void setConnectionTimeout(int connectionTimeout)
{
this.connectionTimeout = connectionTimeout;
}
protected HttpClient constructHttpClient()
{
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient httpClient = new HttpClient(connectionManager);
HttpClientParams params = httpClient.getParams();
params.setBooleanParameter(HttpConnectionParams.TCP_NODELAY, true);
params.setBooleanParameter(HttpConnectionParams.STALE_CONNECTION_CHECK, true);
if (socketTimeout != null)
{
params.setSoTimeout(socketTimeout);
}
HttpConnectionManagerParams connectionManagerParams = httpClient.getHttpConnectionManager().getParams();
connectionManagerParams.setMaxTotalConnections(maxTotalConnections);
connectionManagerParams.setDefaultMaxConnectionsPerHost(maxHostConnections);
connectionManagerParams.setConnectionTimeout(connectionTimeout);
return httpClient;
}
protected HttpClient getHttpsClient()
{
return getHttpsClient(host, sslPort);
}
protected HttpClient getHttpsClient(String httpsHost, int httpsPort)
{
// Configure a custom SSL socket factory that will enforce mutual authentication
HttpClient httpClient = constructHttpClient();
// Default port is 443 for the HostFactory, when including customised port (like 8983) the port name is skipped from "getHostURL" string
HttpHostFactory hostFactory = new HttpHostFactory(new Protocol("https", sslSocketFactory, HttpsURL.DEFAULT_PORT));
httpClient.setHostConfiguration(new HostConfigurationWithHostFactory(hostFactory));
httpClient.getHostConfiguration().setHost(httpsHost, httpsPort, "https");
return httpClient;
}
protected HttpClient getDefaultHttpClient()
{
return getDefaultHttpClient(host, port);
}
protected HttpClient getDefaultHttpClient(String httpHost, int httpPort)
{
HttpClient httpClient = constructHttpClient();
httpClient.getHostConfiguration().setHost(httpHost, httpPort);
return httpClient;
}
protected AlfrescoHttpClient getAlfrescoHttpsClient()
{
AlfrescoHttpClient repoClient = new HttpsClient(getHttpsClient());
return repoClient;
}
protected AlfrescoHttpClient getAlfrescoHttpClient()
{
AlfrescoHttpClient repoClient = new DefaultHttpClient(getDefaultHttpClient());
return repoClient;
}
protected HttpClient getMD5HttpClient(String host, int port)
{
HttpClient httpClient = constructHttpClient();
httpClient.getHostConfiguration().setHost(host, port);
return httpClient;
}
public AlfrescoHttpClient getRepoClient(String host, int port)
{
AlfrescoHttpClient repoClient = null;
if(secureCommsType == SecureCommsType.HTTPS)
{
repoClient = getAlfrescoHttpsClient();
}
else if(secureCommsType == SecureCommsType.NONE)
{
repoClient = getAlfrescoHttpClient();
}
else
{
throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
}
return repoClient;
}
public HttpClient getHttpClient()
{
HttpClient httpClient = null;
if(secureCommsType == SecureCommsType.HTTPS)
{
httpClient = getHttpsClient();
}
else if(secureCommsType == SecureCommsType.NONE)
{
httpClient = getDefaultHttpClient();
}
else
{
throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
}
return httpClient;
}
public HttpClient getHttpClient(String host, int port)
{
HttpClient httpClient = null;
if(secureCommsType == SecureCommsType.HTTPS)
{
httpClient = getHttpsClient(host, port);
}
else if(secureCommsType == SecureCommsType.NONE)
{
httpClient = getDefaultHttpClient(host, port);
}
else
{
throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
}
return httpClient;
}
/**
* A secure client connection to the repository.
*
* @since 4.0
*
*/
class HttpsClient extends AbstractHttpClient
{
public HttpsClient(HttpClient httpClient)
{
super(httpClient);
}
/**
* Send Request to the repository
*/
public Response sendRequest(Request req) throws AuthenticationException, IOException
{
HttpMethod method = super.sendRemoteRequest(req);
return new HttpMethodResponse(method);
}
}
/**
* Simple HTTP client to connect to the Alfresco server. Simply wraps a HttpClient.
*
* @since 4.0
*/
class DefaultHttpClient extends AbstractHttpClient
{
public DefaultHttpClient(HttpClient httpClient)
{
super(httpClient);
}
/**
* Send Request to the repository
*/
public Response sendRequest(Request req) throws AuthenticationException, IOException
{
HttpMethod method = super.sendRemoteRequest(req);
return new HttpMethodResponse(method);
}
}
static class SecureHttpMethodResponse extends HttpMethodResponse
{
protected HostConfiguration hostConfig;
protected EncryptionUtils encryptionUtils;
// Need to get as a byte array because we need to read the request twice, once for authentication
// and again by the web service.
protected byte[] decryptedBody;
public SecureHttpMethodResponse(HttpMethod method, HostConfiguration hostConfig,
EncryptionUtils encryptionUtils) throws AuthenticationException, IOException
{
super(method);
this.hostConfig = hostConfig;
this.encryptionUtils = encryptionUtils;
if(method.getStatusCode() == HttpStatus.SC_OK)
{
this.decryptedBody = encryptionUtils.decryptResponseBody(method);
// authenticate the response
if(!authenticate())
{
throw new AuthenticationException(method);
}
}
}
protected boolean authenticate() throws IOException
{
return encryptionUtils.authenticateResponse(method, hostConfig.getHost(), decryptedBody);
}
public InputStream getContentAsStream() throws IOException
{
if(decryptedBody != null)
{
return new ByteArrayInputStream(decryptedBody);
}
else
{
return null;
}
}
}
private static class HttpHostFactory
{
private Map<String, Protocol> protocols;
public HttpHostFactory(Protocol httpsProtocol)
{
protocols = new HashMap<String, Protocol>(2);
protocols.put("https", httpsProtocol);
}
/** Get a host for the given parameters. This method need not be thread-safe. */
public HttpHost getHost(String host, int port, String scheme)
{
if(scheme == null)
{
scheme = "http";
}
Protocol protocol = protocols.get(scheme);
if(protocol == null)
{
protocol = Protocol.getProtocol("http");
if(protocol == null)
{
throw new IllegalArgumentException("Unrecognised scheme parameter");
}
}
return new HttpHost(host, port, protocol);
}
}
private static class HostConfigurationWithHostFactory extends HostConfiguration
{
private final HttpHostFactory factory;
public HostConfigurationWithHostFactory(HttpHostFactory factory)
{
this.factory = factory;
}
public synchronized void setHost(String host, int port, String scheme)
{
setHost(factory.getHost(host, port, scheme));
}
public synchronized void setHost(String host, int port)
{
setHost(factory.getHost(host, port, "http"));
}
@SuppressWarnings("unused")
public synchronized void setHost(URI uri)
{
try {
setHost(uri.getHost(), uri.getPort(), uri.getScheme());
} catch(URIException e) {
throw new IllegalArgumentException(e.toString());
}
}
}
/**
* An extension of the DefaultHttpParamsFactory that uses a RRW lock pattern rather than
* full synchronization around the parameter CRUD - to avoid locking on many reads.
*
* @author Kevin Roast
*/
public static class NonBlockingHttpParamsFactory extends DefaultHttpParamsFactory
{
private volatile HttpParams httpParams;
/* (non-Javadoc)
* @see org.apache.commons.httpclient.params.DefaultHttpParamsFactory#getDefaultParams()
*/
@Override
public HttpParams getDefaultParams()
{
if (httpParams == null)
{
synchronized (this)
{
if (httpParams == null)
{
httpParams = createParams();
}
}
}
return httpParams;
}
/**
* NOTE: This is a copy of the code in {@link DefaultHttpParamsFactory}
* Unfortunately this is required because although the factory pattern allows the
* override of the default param creation, it does not allow the class of the actual
* HttpParam implementation to be changed.
*/
@Override
protected HttpParams createParams()
{
HttpClientParams params = new NonBlockingHttpParams(null);
params.setParameter(HttpMethodParams.USER_AGENT, "Spring Surf via Apache HttpClient/3.1");
params.setVersion(HttpVersion.HTTP_1_1);
params.setConnectionManagerClass(SimpleHttpConnectionManager.class);
params.setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
params.setHttpElementCharset("US-ASCII");
params.setContentCharset("ISO-8859-1");
params.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
List<String> datePatterns = Arrays.asList(
new String[] {
DateUtil.PATTERN_RFC1123,
DateUtil.PATTERN_RFC1036,
DateUtil.PATTERN_ASCTIME,
"EEE, dd-MMM-yyyy HH:mm:ss z",
"EEE, dd-MMM-yyyy HH-mm-ss z",
"EEE, dd MMM yy HH:mm:ss z",
"EEE dd-MMM-yyyy HH:mm:ss z",
"EEE dd MMM yyyy HH:mm:ss z",
"EEE dd-MMM-yyyy HH-mm-ss z",
"EEE dd-MMM-yy HH:mm:ss z",
"EEE dd MMM yy HH:mm:ss z",
"EEE,dd-MMM-yy HH:mm:ss z",
"EEE,dd-MMM-yyyy HH:mm:ss z",
"EEE, dd-MM-yyyy HH:mm:ss z",
}
);
params.setParameter(HttpMethodParams.DATE_PATTERNS, datePatterns);
String agent = null;
try
{
agent = System.getProperty("httpclient.useragent");
}
catch (SecurityException ignore)
{
}
if (agent != null)
{
params.setParameter(HttpMethodParams.USER_AGENT, agent);
}
String preemptiveDefault = null;
try
{
preemptiveDefault = System.getProperty("httpclient.authentication.preemptive");
}
catch (SecurityException ignore)
{
}
if (preemptiveDefault != null)
{
preemptiveDefault = preemptiveDefault.trim().toLowerCase();
if (preemptiveDefault.equals("true"))
{
params.setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, Boolean.TRUE);
}
else if (preemptiveDefault.equals("false"))
{
params.setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, Boolean.FALSE);
}
}
String defaultCookiePolicy = null;
try
{
defaultCookiePolicy = System.getProperty("apache.commons.httpclient.cookiespec");
}
catch (SecurityException ignore)
{
}
if (defaultCookiePolicy != null)
{
if ("COMPATIBILITY".equalsIgnoreCase(defaultCookiePolicy))
{
params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
}
else if ("NETSCAPE_DRAFT".equalsIgnoreCase(defaultCookiePolicy))
{
params.setCookiePolicy(CookiePolicy.NETSCAPE);
}
else if ("RFC2109".equalsIgnoreCase(defaultCookiePolicy))
{
params.setCookiePolicy(CookiePolicy.RFC_2109);
}
}
return params;
}
}
/**
* @author Kevin Roast
*/
public static class NonBlockingHttpParams extends HttpClientParams
{
private HashMap<String, Object> parameters = new HashMap<String, Object>(8);
private ReadWriteLock paramLock = new ReentrantReadWriteLock();
public NonBlockingHttpParams()
{
super();
}
public NonBlockingHttpParams(HttpParams defaults)
{
super(defaults);
}
@Override
public Object getParameter(final String name)
{
// See if the parameter has been explicitly defined
Object param = null;
paramLock.readLock().lock();
try
{
param = this.parameters.get(name);
}
finally
{
paramLock.readLock().unlock();
}
if (param == null)
{
// If not, see if defaults are available
HttpParams defaults = getDefaults();
if (defaults != null)
{
// Return default parameter value
param = defaults.getParameter(name);
}
}
return param;
}
@Override
public void setParameter(final String name, final Object value)
{
paramLock.writeLock().lock();
try
{
this.parameters.put(name, value);
}
finally
{
paramLock.writeLock().unlock();
}
}
@Override
public boolean isParameterSetLocally(final String name)
{
paramLock.readLock().lock();
try
{
return (this.parameters.get(name) != null);
}
finally
{
paramLock.readLock().unlock();
}
}
@Override
public void clear()
{
paramLock.writeLock().lock();
try
{
this.parameters.clear();
}
finally
{
paramLock.writeLock().unlock();
}
}
@Override
public Object clone() throws CloneNotSupportedException
{
NonBlockingHttpParams clone = (NonBlockingHttpParams)super.clone();
paramLock.readLock().lock();
try
{
clone.parameters = (HashMap) this.parameters.clone();
}
finally
{
paramLock.readLock().unlock();
}
clone.setDefaults(getDefaults());
return clone;
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethod;
/**
*
* @since 4.0
*
*/
public class HttpMethodResponse implements Response
{
protected HttpMethod method;
public HttpMethodResponse(HttpMethod method) throws IOException
{
this.method = method;
}
public void release()
{
method.releaseConnection();
}
public InputStream getContentAsStream() throws IOException
{
return method.getResponseBodyAsStream();
}
public String getContentType()
{
return getHeader("Content-Type");
}
public String getHeader(String name)
{
Header header = method.getResponseHeader(name);
return (header != null) ? header.getValue() : null;
}
public int getStatus()
{
return method.getStatusCode();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
/**
*
* @since 4.0
*
*/
public class MD5EncryptionParameters
{
private String cipherAlgorithm;
private long messageTimeout;
private String macAlgorithm;
public MD5EncryptionParameters()
{
}
public MD5EncryptionParameters(String cipherAlgorithm,
Long messageTimeout, String macAlgorithm)
{
this.cipherAlgorithm = cipherAlgorithm;
this.messageTimeout = messageTimeout;
this.macAlgorithm = macAlgorithm;
}
public String getCipherAlgorithm()
{
return cipherAlgorithm;
}
public void setCipherAlgorithm(String cipherAlgorithm)
{
this.cipherAlgorithm = cipherAlgorithm;
}
public long getMessageTimeout()
{
return messageTimeout;
}
public String getMacAlgorithm()
{
return macAlgorithm;
}
public void setMessageTimeout(long messageTimeout)
{
this.messageTimeout = messageTimeout;
}
public void setMacAlgorithm(String macAlgorithm)
{
this.macAlgorithm = macAlgorithm;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.UnsupportedEncodingException;
/**
* HTTP POST Request
*
* @since 4.0
*/
public class PostRequest extends Request
{
public PostRequest(String uri, String post, String contentType)
throws UnsupportedEncodingException
{
super("post", uri);
setBody(getEncoding() == null ? post.getBytes() : post.getBytes(getEncoding()));
setType(contentType);
}
public PostRequest(String uri, byte[] post, String contentType)
{
super("post", uri);
setBody(post);
setType(contentType);
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.util.Map;
/**
*
* @since 4.0
*
*/
public class Request
{
private String method;
private String uri;
private Map<String, String> args;
private Map<String, String> headers;
private byte[] body;
private String encoding = "UTF-8";
private String contentType;
public Request(Request req)
{
this.method = req.method;
this.uri= req.uri;
this.args = req.args;
this.headers = req.headers;
this.body = req.body;
this.encoding = req.encoding;
this.contentType = req.contentType;
}
public Request(String method, String uri)
{
this.method = method;
this.uri = uri;
}
public String getMethod()
{
return method;
}
public String getUri()
{
return uri;
}
public String getFullUri()
{
// calculate full uri
String fullUri = uri == null ? "" : uri;
if (args != null && args.size() > 0)
{
char prefix = (uri.indexOf('?') == -1) ? '?' : '&';
for (Map.Entry<String, String> arg : args.entrySet())
{
fullUri += prefix + arg.getKey() + "=" + (arg.getValue() == null ? "" : arg.getValue());
prefix = '&';
}
}
return fullUri;
}
public Request setArgs(Map<String, String> args)
{
this.args = args;
return this;
}
public Map<String, String> getArgs()
{
return args;
}
public Request setHeaders(Map<String, String> headers)
{
this.headers = headers;
return this;
}
public Map<String, String> getHeaders()
{
return headers;
}
public Request setBody(byte[] body)
{
this.body = body;
return this;
}
public byte[] getBody()
{
return body;
}
public Request setEncoding(String encoding)
{
this.encoding = encoding;
return this;
}
public String getEncoding()
{
return encoding;
}
public Request setType(String contentType)
{
this.contentType = contentType;
return this;
}
public String getType()
{
return contentType;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.IOException;
import java.io.InputStream;
/**
*
* @since 4.0
*
*/
public interface Response
{
public InputStream getContentAsStream() throws IOException;
public String getHeader(String name);
public String getContentType();
public int getStatus();
// public Long getRequestDuration();
public void release();
}

View File

@@ -0,0 +1,149 @@
package org.alfresco.httpclient;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AlgorithmParameters;
import org.alfresco.encryption.EncryptionUtils;
import org.alfresco.encryption.Encryptor;
import org.alfresco.encryption.KeyProvider;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.util.Pair;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Simple HTTP client to connect to the Alfresco server.
*
* @since 4.0
*/
public class SecureHttpClient //extends AbstractHttpClient
{
// private static final Log logger = LogFactory.getLog(SecureHttpClient.class);
//
// private Encryptor encryptor;
// private EncryptionUtils encryptionUtils;
// private EncryptionService encryptionService;
// private EncryptionParameters encryptionParameters;
//
// /**
// * For testing purposes.
// *
// * @param solrResourceLoader
// * @param alfrescoHost
// * @param alfrescoPort
// * @param encryptionParameters
// */
// public SecureHttpClient(HttpClientFactory httpClientFactory, String host, int port, EncryptionService encryptionService)
// {
// super(httpClientFactory, host, port);
// this.encryptionUtils = encryptionService.getEncryptionUtils();
// this.encryptor = encryptionService.getEncryptor();
// this.encryptionService = encryptionService;
// this.encryptionParameters = encryptionService.getEncryptionParameters();
// }
//
// public SecureHttpClient(HttpClientFactory httpClientFactory, KeyResourceLoader keyResourceLoader, String host, int port,
// EncryptionParameters encryptionParameters)
// {
// super(httpClientFactory, host, port);
// this.encryptionParameters = encryptionParameters;
// this.encryptionService = new EncryptionService(alfrescoHost, alfrescoPort, keyResourceLoader, encryptionParameters);
// this.encryptionUtils = encryptionService.getEncryptionUtils();
// this.encryptor = encryptionService.getEncryptor();
// }
//
// protected HttpMethod createMethod(Request req) throws IOException
// {
// byte[] message = null;
// HttpMethod method = super.createMethod(req);
//
// if(req.getMethod().equalsIgnoreCase("POST"))
// {
// message = req.getBody();
// // encrypt body
// Pair<byte[], AlgorithmParameters> encrypted = encryptor.encrypt(KeyProvider.ALIAS_SOLR, null, message);
// encryptionUtils.setRequestAlgorithmParameters(method, encrypted.getSecond());
//
// ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(encrypted.getFirst(), "application/octet-stream");
// ((PostMethod)method).setRequestEntity(requestEntity);
// }
//
// encryptionUtils.setRequestAuthentication(method, message);
//
// return method;
// }
//
// protected HttpMethod sendRemoteRequest(Request req) throws AuthenticationException, IOException
// {
// HttpMethod method = super.sendRemoteRequest(req);
//
// // check that the request returned with an ok status
// if(method.getStatusCode() == HttpStatus.SC_UNAUTHORIZED)
// {
// throw new AuthenticationException(method);
// }
//
// return method;
// }
//
// /**
// * Send Request to the repository
// */
// public Response sendRequest(Request req) throws AuthenticationException, IOException
// {
// HttpMethod method = super.sendRemoteRequest(req);
// return new SecureHttpMethodResponse(method, httpClient.getHostConfiguration(), encryptionUtils);
// }
//
// public static class SecureHttpMethodResponse extends HttpMethodResponse
// {
// protected HostConfiguration hostConfig;
// protected EncryptionUtils encryptionUtils;
// // Need to get as a byte array because we need to read the request twice, once for authentication
// // and again by the web service.
// protected byte[] decryptedBody;
//
// public SecureHttpMethodResponse(HttpMethod method, HostConfiguration hostConfig,
// EncryptionUtils encryptionUtils) throws AuthenticationException, IOException
// {
// super(method);
// this.hostConfig = hostConfig;
// this.encryptionUtils = encryptionUtils;
//
// if(method.getStatusCode() == HttpStatus.SC_OK)
// {
// this.decryptedBody = encryptionUtils.decryptResponseBody(method);
// // authenticate the response
// if(!authenticate())
// {
// throw new AuthenticationException(method);
// }
// }
// }
//
// protected boolean authenticate() throws IOException
// {
// return encryptionUtils.authenticateResponse(method, hostConfig.getHost(), decryptedBody);
// }
//
// public InputStream getContentAsStream() throws IOException
// {
// if(decryptedBody != null)
// {
// return new ByteArrayInputStream(decryptedBody);
// }
// else
// {
// return null;
// }
// }
// }
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.i18n;
import java.util.List;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Resource bundle bootstrap component.
* <p>
* Provides a convenient way to make resource bundles available via Spring config.
*
* @author Roy Wetherall
*/
public class ResourceBundleBootstrapComponent
{
/**
* Set the resource bundles to be registered. This should be a list of resource
* bundle base names whose content will be made available across the repository.
*
* @param resourceBundles the resource bundles
*/
public void setResourceBundles(List<String> resourceBundles)
{
for (String resourceBundle : resourceBundles)
{
I18NUtil.registerResourceBundle(resourceBundle);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.processor;
/**
* Interface for Proccessor classes - such as Template or Scripting Processors.
*
* @author Roy Wetherall
*/
public interface Processor
{
/**
* Get the name of the processor
*
* @return the name of the processor
*/
public String getName();
/**
* The file extension that the processor is associated with, null if none.
*
* @return the extension
*/
public String getExtension();
/**
* Registers a processor extension with the processor
*
* @param processorExtension the process extension
*/
public void registerProcessorExtension(ProcessorExtension processorExtension);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.processor;
/**
* Interface to represent a server side script implementation
*
* @author Roy Wetherall
*/
public interface ProcessorExtension
{
/**
* Returns the name of the extension
*
* @return the name of the extension
*/
String getExtensionName();
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.List;
/**
* Caching support extension for {@link CannedQueryFactory} implementations.
* <p/>
* Depending on the parameters provided, this class may choose to pick up existing results
* and re-use them for later page requests; the client will not have knowledge of the
* shortcuts.
*
* TODO: This is work-in-progress
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractCachingCannedQueryFactory<R> extends AbstractCannedQueryFactory<R>
{
/**
* Base implementation that provides a caching facade around the query.
*
* @return a decoraded facade query that will cache query results for later paging requests
*/
@Override
public final CannedQuery<R> getCannedQuery(CannedQueryParameters parameters)
{
throw new UnsupportedOperationException();
}
/**
* Derived classes must implement this method to provide the raw query that supports the given
* parameters. All requests must be serviced without any further caching in order to prevent
* duplicate caching.
*
* @param parameters the query parameters as given by the client
* @return the query that will generate the results
*/
protected abstract CannedQuery<R> getCannedQueryImpl(CannedQueryParameters parameters);
private class CannedQueryCacheFacade<R> extends AbstractCannedQuery<R>
{
private final AbstractCannedQuery<R> delegate;
private CannedQueryCacheFacade(CannedQueryParameters params, AbstractCannedQuery<R> delegate)
{
super(params);
this.delegate = delegate;
}
@Override
protected List<R> queryAndFilter(CannedQueryParameters parameters)
{
// Copy the parameters and remove all references to paging.
// The underlying query will return full or filtered results (possibly also sorted)
// but will not apply page limitations
throw new UnsupportedOperationException();
}
}
}

View File

@@ -0,0 +1,347 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
/**
* Basic support for canned query implementations.
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractCannedQuery<R> implements CannedQuery<R>
{
private final CannedQueryParameters parameters;
private final String queryExecutionId;
private CannedQueryResults<R> results;
/**
* Construct the canned query given the original parameters applied.
* <p/>
* A random GUID query execution ID will be generated.
*
* @param parameters the original query parameters
*/
protected AbstractCannedQuery(CannedQueryParameters parameters)
{
ParameterCheck.mandatory("parameters", parameters);
this.parameters = parameters;
this.queryExecutionId = GUID.generate();
}
@Override
public CannedQueryParameters getParameters()
{
return parameters;
}
@Override
public String toString()
{
return "AbstractCannedQuery [parameters=" + parameters + ", class=" + this.getClass() + "]";
}
@Override
public synchronized final CannedQueryResults<R> execute()
{
// Check that we are not requerying
if (results != null)
{
throw new IllegalStateException(
"This query instance has already by used." +
" It can only be used to query once.");
}
// Get the raw query results
List<R> rawResults = queryAndFilter(parameters);
if (rawResults == null)
{
throw new AlfrescoRuntimeException("Execution returned 'null' results");
}
// Apply sorting
if (isApplyPostQuerySorting())
{
rawResults = applyPostQuerySorting(rawResults, parameters.getSortDetails());
}
// Apply permissions
if (isApplyPostQueryPermissions())
{
// Work out the number of results required
int requestedCount = parameters.getResultsRequired();
rawResults = applyPostQueryPermissions(rawResults, requestedCount);
}
// Get total count
final Pair<Integer, Integer> totalCount = getTotalResultCount(rawResults);
// Apply paging
CannedQueryPageDetails pagingDetails = parameters.getPageDetails();
List<List<R>> pages = Collections.singletonList(rawResults);
if (isApplyPostQueryPaging())
{
pages = applyPostQueryPaging(rawResults, pagingDetails);
}
// Construct results object
final List<List<R>> finalPages = pages;
// Has more items beyond requested pages ? ... ie. at least one more page (with at least one result)
final boolean hasMoreItems = (rawResults.size() > pagingDetails.getResultsRequiredForPaging());
results = new CannedQueryResults<R>()
{
@Override
public CannedQuery<R> getOriginatingQuery()
{
return AbstractCannedQuery.this;
}
@Override
public String getQueryExecutionId()
{
return queryExecutionId;
}
@Override
public Pair<Integer, Integer> getTotalResultCount()
{
if (parameters.getTotalResultCountMax() > 0)
{
return totalCount;
}
else
{
throw new IllegalStateException("Total results were not requested in parameters.");
}
}
@Override
public int getPagedResultCount()
{
int finalPagedCount = 0;
for (List<R> page : finalPages)
{
finalPagedCount += page.size();
}
return finalPagedCount;
}
@Override
public int getPageCount()
{
return finalPages.size();
}
@Override
public R getSingleResult()
{
if (finalPages.size() != 1 && finalPages.get(0).size() != 1)
{
throw new IllegalStateException("There must be exactly one page of one result available.");
}
return finalPages.get(0).get(0);
}
@Override
public List<R> getPage()
{
if (finalPages.size() != 1)
{
throw new IllegalStateException("There must be exactly one page of results available.");
}
return finalPages.get(0);
}
@Override
public List<List<R>> getPages()
{
return finalPages;
}
@Override
public boolean hasMoreItems()
{
return hasMoreItems;
}
};
return results;
}
/**
* Implement the basic query, returning either filtered or all results.
* <p/>
* The implementation may optimally select, filter, sort and apply permissions.
* If not, however, the subsequent post-query methods
* ({@link #applyPostQuerySorting(List, CannedQuerySortDetails)},
* {@link #applyPostQueryPermissions(List, int)} and
* {@link #applyPostQueryPaging(List, CannedQueryPageDetails)}) can
* be used to trim the results as required.
*
* @param parameters the full parameters to be used for execution
*/
protected abstract List<R> queryAndFilter(CannedQueryParameters parameters);
/**
* Override to get post-query calls to do sorting.
*
* @return <tt>true</tt> to get a post-query call to sort (default <tt>false</tt>)
*/
protected boolean isApplyPostQuerySorting()
{
return false;
}
/**
* Called before {@link #applyPostQueryPermissions(List, int)} to allow the results to be sorted prior to permission checks.
* Note that the query implementation may optimally sort results during retrieval, in which case this method does not need to be implemented.
*
* @param results the results to sort
* @param sortDetails details of the sorting requirements
* @return the results according to the new sort order
*/
protected List<R> applyPostQuerySorting(List<R> results, CannedQuerySortDetails sortDetails)
{
throw new UnsupportedOperationException("Override this method if post-query sorting is required.");
}
/**
* Override to get post-query calls to apply permission filters.
*
* @return <tt>true</tt> to get a post-query call to apply permissions (default <tt>false</tt>)
*/
protected boolean isApplyPostQueryPermissions()
{
return false;
}
/**
* Called after the query to filter out results based on permissions.
* Note that the query implementation may optimally only select results
* based on available privileges, in which case this method does not need to be implemented.
* <p/>
* Permission evaluations should continue until the requested number of results are retrieved
* or all available results have been examined.
*
* @param results the results to apply permissions to
* @param requestedCount the minimum number of results to pass the permission checks
* in order to fully satisfy the paging requirements
* @return the remaining results (as a single "page") after permissions have been applied
*/
protected List<R> applyPostQueryPermissions(List<R> results, int requestedCount)
{
throw new UnsupportedOperationException("Override this method if post-query filtering is required.");
}
/**
* Get the total number of available results after querying, filtering, sorting and permission checking.
* <p/>
* The default implementation assumes that the given results are the final total possible.
*
* @param results the results after filtering and sorting, but before paging
* @return pair representing (a) the total number of results and
* (b) the estimated (or actual) number of maximum results
* possible for this query.
*
* @see CannedQueryParameters#getTotalResultCountMax()
*/
protected Pair<Integer, Integer> getTotalResultCount(List<R> results)
{
Integer size = results.size();
return new Pair<Integer, Integer>(size, size);
}
/**
* Override to get post-query calls to do pull out paged results.
*
* @return <tt>true</tt> to get a post-query call to page (default <tt>true</tt>)
*/
protected boolean isApplyPostQueryPaging()
{
return true;
}
/**
* Called after the {@link #applyPostQuerySorting(List, CannedQuerySortDetails) sorting phase} to pull out results specific
* to the required pages. Note that the query implementation may optimally
* create page-specific results, in which case this method does not need to be implemented.
* <p/>
* The base implementation assumes that results are not paged and that the current results
* are all the available results i.e. that paging still needs to be applied.
*
* @param results full results (all or excess pages)
* @param pageDetails details of the paging requirements
* @return the specific page of results as per the query parameters
*/
protected List<List<R>> applyPostQueryPaging(List<R> results, CannedQueryPageDetails pageDetails)
{
int skipResults = pageDetails.getSkipResults();
int pageSize = pageDetails.getPageSize();
int pageCount = pageDetails.getPageCount();
int pageNumber = pageDetails.getPageNumber();
int availableResults = results.size();
int totalResults = pageSize * pageCount;
int firstResult = skipResults + ((pageNumber-1) * pageSize); // first of window
List<List<R>> pages = new ArrayList<List<R>>(pageCount);
// First some shortcuts
if (skipResults == 0 && pageSize > availableResults)
{
return Collections.singletonList(results); // Requesting more results in one page than are available
}
else if (firstResult > availableResults)
{
return pages; // Start of first page is after all results
}
// Build results
Iterator<R> iterator = results.listIterator(firstResult);
int countTotal = 0;
List<R> page = new ArrayList<R>(Math.min(results.size(), pageSize)); // Prevent memory blow-out
pages.add(page);
while (iterator.hasNext() && countTotal < totalResults)
{
if (page.size() == pageSize)
{
// Create a page and add it to the results
page = new ArrayList<R>(pageSize);
pages.add(page);
}
R next = iterator.next();
page.add(next);
countTotal++;
}
// Done
return pages;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import org.alfresco.util.GUID;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.registry.NamedObjectRegistry;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
/**
* Basic services for {@link CannedQueryFactory} implementations.
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractCannedQueryFactory<R> implements CannedQueryFactory<R>, InitializingBean, BeanNameAware
{
private String name;
@SuppressWarnings("rawtypes")
private NamedObjectRegistry<CannedQueryFactory> registry;
/**
* Set the name with which to {@link #setRegistry(NamedObjectRegistry) register}
* @param name the name of the bean
*/
public void setBeanName(String name)
{
this.name = name;
}
/**
* Set the registry with which to register
*/
@SuppressWarnings("rawtypes")
public void setRegistry(NamedObjectRegistry<CannedQueryFactory> registry)
{
this.registry = registry;
}
/**
* Registers the instance
*/
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "name", name);
PropertyCheck.mandatory(this, "registry", registry);
registry.register(name, this);
}
/**
* Helper method to construct a unique query execution ID based on the
* instance of the factory and the parameters provided.
*
* @param parameters the query parameters
* @return a unique query instance ID
*/
protected String getQueryExecutionId(CannedQueryParameters parameters)
{
// Create a GUID
String uuid = name + "-" + GUID.generate();
return uuid;
}
/**
* {@inheritDoc}
*/
@Override
public CannedQuery<R> getCannedQuery(Object parameterBean, int skipResults, int pageSize, String queryExecutionId)
{
return getCannedQuery(new CannedQueryParameters(parameterBean, skipResults, pageSize, queryExecutionId));
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Interface for named query implementations. These are queries that encapsulate varying
* degrees of functionality, but ultimately provide support for paging results.
* <p/>
* Note that each instance of the query is stateful and cannot be reused.
*
* @param <R> the query result type
*
* @author Derek Hulley
* @since 4.0
*/
public interface CannedQuery<R>
{
/**
* Get the original parameters used to generate the query.
*
* @return the parameters used to obtain the named query.
*/
CannedQueryParameters getParameters();
/**
* Execute the named query, which was provided to support the
* {@link #getParameters() parameters} originally provided.
* <p/>
* <b>Note: This method can only be used once</b>; to requery, get a new
* instance from the {@link CannedQueryFactory factory}.
*
* @return the query results
*
* @throws IllegalStateException on second and subsequent calls to this method
*/
CannedQueryResults<R> execute();
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import org.alfresco.error.AlfrescoRuntimeException;
/**
* Exception generated by failures to execute canned queries.
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQueryException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = -4985399145374964458L;
/**
* @param msg the message
*/
public CannedQueryException(String msg)
{
super(msg);
}
/**
* @param msg the message
* @param cause the exception cause
*/
public CannedQueryException(String msg, Throwable cause)
{
super(msg, cause);
}
/**
* @param msgId the message id
* @param msgParams the message parameters
*/
public CannedQueryException(String msgId, Object[] msgParams)
{
super(msgId, msgParams);
}
/**
* @param msgId the message id
* @param msgParams the message parameters
* @param cause the exception cause
*/
public CannedQueryException(String msgId, Object[] msgParams, Throwable cause)
{
super(msgId, msgParams, cause);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Interface for factory implementations for producing instances of {@link CannedQuery}
* based on all the query parameters.
*
* @param <R> the query result type
*
* @author Derek Hulley, janv
* @since 4.0
*/
public interface CannedQueryFactory<R>
{
/**
* Retrieve an instance of a {@link CannedQuery} based on the full range of
* available parameters.
*
* @param parameters the full query parameters
* @return an implementation that will execute the query
*/
CannedQuery<R> getCannedQuery(CannedQueryParameters parameters);
/**
* Retrieve an instance of a {@link CannedQuery} based on limited parameters.
*
* @param parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
* @param skipResults results to skip before page
* @param pageSize the size of page - ie. max items (if skipResults = 0)
* @param queryExecutionId ID of a previously-executed query to be used during follow-up
* page requests - <tt>null</tt> if not available
* @return an implementation that will execute the query
*/
CannedQuery<R> getCannedQuery(Object parameterBean, int skipResults, int pageSize, String queryExecutionId);
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Details for canned queries supporting paged results.
* <p/>
* Results are {@link #skipResults skipped}, chopped into pages of
* {@link #pageSize appropriate size} before the {@link #pageCount start page}
* and {@link #pageNumber number} are returned.
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQueryPageDetails
{
public static final int DEFAULT_SKIP_RESULTS = 0;
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
public static final int DEFAULT_PAGE_NUMBER = 1;
public static final int DEFAULT_PAGE_COUNT = 1;
private final int skipResults;
private final int pageSize;
private final int pageNumber;
private final int pageCount;
/**
* Construct with defaults
* <ul>
* <li><b>skipResults:</b> {@link #DEFAULT_SKIP_RESULTS}</li>
* <li><b>pageSize:</b> {@link #DEFAULT_PAGE_SIZE}</li>
* <li><b>pageNumber:</b> {@link #DEFAULT_PAGE_NUMBER}</li>
* <li><b>pageCount:</b> {@link #DEFAULT_PAGE_COUNT}</li>
* </ul>
*/
public CannedQueryPageDetails()
{
this(DEFAULT_SKIP_RESULTS, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_COUNT);
}
/**
* Construct with defaults
* <ul>
* <li><b>pageNumber:</b> {@link #DEFAULT_PAGE_NUMBER}</li>
* <li><b>pageCount:</b> {@link #DEFAULT_PAGE_COUNT}</li>
* </ul>
* @param skipResults results to skip before <i>page one</i>
* (default <b>{@link #DEFAULT_SKIP_RESULTS}</b>)
* @param pageSize the size of each page
* (default <b>{@link #DEFAULT_PAGE_SIZE}</b>)
*/
public CannedQueryPageDetails(int skipResults, int pageSize)
{
this (skipResults, pageSize, DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_COUNT);
}
/**
* @param skipResults results to skip before <i>page one</i>
* (default <b>{@link #DEFAULT_SKIP_RESULTS}</b>)
* @param pageSize the size of each page
* (default <b>{@link #DEFAULT_PAGE_SIZE}</b>)
* @param pageNumber the first page number to return
* (default <b>{@link #DEFAULT_PAGE_NUMBER}</b>)
* @param pageCount the number of pages to return
* (default <b>{@link #DEFAULT_PAGE_COUNT}</b>)
*/
public CannedQueryPageDetails(int skipResults, int pageSize, int pageNumber, int pageCount)
{
this.skipResults = skipResults;
this.pageSize = pageSize;
this.pageNumber = pageNumber;
this.pageCount = pageCount;
// Do some checks
if (skipResults < 0)
{
throw new IllegalArgumentException("Cannot skip fewer than 0 results.");
}
if (pageSize < 1)
{
throw new IllegalArgumentException("pageSize must be greater than zero.");
}
if (pageNumber < 1)
{
throw new IllegalArgumentException("pageNumber must be greater than zero.");
}
if (pageCount < 1)
{
throw new IllegalArgumentException("pageCount must be greater than zero.");
}
}
/**
* Helper constructor to transform a paging request into the Canned Query form.
*
* @param pagingRequest the paging details
*/
public CannedQueryPageDetails(PagingRequest pagingRequest)
{
this(pagingRequest.getSkipCount(), pagingRequest.getMaxItems());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("NamedQueryPageDetails ")
.append("[skipResults=").append(skipResults)
.append(", pageSize=").append(pageSize)
.append(", pageCount=").append(pageCount)
.append(", pageNumber=").append(pageNumber)
.append("]");
return sb.toString();
}
/**
* Get the number of query results to skip before applying further page parameters
* @return results to skip before <i>page one</i>
*/
public int getSkipResults()
{
return skipResults;
}
/**
* Get the size of each page
* @return the size of each page
*/
public int getPageSize()
{
return pageSize;
}
/**
* Get the first page number to return
* @return the first page number to return
*/
public int getPageNumber()
{
return pageNumber;
}
/**
* Get the total number of pages to return
* @return the number of pages to return
*/
public int getPageCount()
{
return pageCount;
}
/**
* Calculate the number of results that would be required to satisy this paging request.
* Note that the skip size can significantly increase this number even if the page sizes
* are small.
*
* @return the number of results required for proper paging
*/
public int getResultsRequiredForPaging()
{
int tmp = skipResults + pageCount * pageSize;
if(tmp < 0)
{
// overflow
return Integer.MAX_VALUE;
}
else
{
return tmp;
}
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Parameters defining the {@link CannedQuery named query} to execute.
* <p/>
* The implementations of the underlying queries may be vastly different
* depending on seemingly-minor variations in the parameters; only set the
* parameters that are required.
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQueryParameters
{
public static final int DEFAULT_TOTAL_COUNT_MAX = 0; // default 0 => don't request total count
private final Object parameterBean;
private final CannedQueryPageDetails pageDetails;
private final CannedQuerySortDetails sortDetails;
private final int totalResultCountMax;
private final String queryExecutionId;
/**
* <ul>
* <li><b>pageDetails</b>: <tt>null</tt></li>
* <li><b>sortDetails</b>: <tt>null</tt></li>
* <li><b>totalResultCountMax</b>: <tt>0</tt></li>
* <li><b>queryExecutionId</b>: <tt>null</tt></li>
* </ul>
*
*/
public CannedQueryParameters(Object parameterBean)
{
this (parameterBean, null, null, DEFAULT_TOTAL_COUNT_MAX, null);
}
/**
* Defaults:
* <ul>
* <li><b>pageDetails.pageNumber</b>: <tt>1</tt></li>
* <li><b>pageDetails.pageCount</b>: <tt>1</tt></li>
* <li><b>totalResultCountMax</b>: <tt>0</tt></li>
* </ul>
*
*/
public CannedQueryParameters(
Object parameterBean,
int skipResults,
int pageSize,
String queryExecutionId)
{
this (
parameterBean,
new CannedQueryPageDetails(skipResults, pageSize, CannedQueryPageDetails.DEFAULT_PAGE_NUMBER, CannedQueryPageDetails.DEFAULT_PAGE_COUNT),
null,
DEFAULT_TOTAL_COUNT_MAX,
queryExecutionId);
}
/**
* Defaults:
* <ul>
* <li><b>totalResultCountMax</b>: <tt>0</tt></li>
* <li><b>queryExecutionId</b>: <tt>null</tt></li>
* </ul>
*
*/
public CannedQueryParameters(
Object parameterBean,
CannedQueryPageDetails pageDetails,
CannedQuerySortDetails sortDetails)
{
this (parameterBean, pageDetails, sortDetails, DEFAULT_TOTAL_COUNT_MAX, null);
}
/**
* Construct all the parameters for executing a named query, using values from the
* {@link PagingRequest}.
*
* @param parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
* @param sortDetails the type of sorting to be applied or <tt>null</tt> for none
* @param pagingRequest the type of paging to be applied or <tt>null</tt> for none
*/
public CannedQueryParameters(
Object parameterBean,
CannedQuerySortDetails sortDetails,
PagingRequest pagingRequest)
{
this (
parameterBean,
pagingRequest == null ? null : new CannedQueryPageDetails(pagingRequest),
sortDetails,
pagingRequest == null ? 0 : pagingRequest.getRequestTotalCountMax(),
pagingRequest == null ? null : pagingRequest.getQueryExecutionId());
}
/**
* Construct all the parameters for executing a named query. Note that the allowable values
* for the arguments depend on the specific query being executed.
*
* @param parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
* @param pageDetails the type of paging to be applied or <tt>null</tt> for none
* @param sortDetails the type of sorting to be applied or <tt>null</tt> for none
* @param totalResultCountMax greater than zero if the query should not only return the required rows
* but should also return the total number of possible rows up to
* the given maximum.
* @param queryExecutionId ID of a previously-executed query to be used during follow-up
* page requests - <tt>null</tt> if not available
*/
@SuppressWarnings("unchecked")
public CannedQueryParameters(
Object parameterBean,
CannedQueryPageDetails pageDetails,
CannedQuerySortDetails sortDetails,
int totalResultCountMax,
String queryExecutionId)
{
if (totalResultCountMax < 0)
{
throw new IllegalArgumentException("totalResultCountMax cannot be negative.");
}
this.parameterBean = parameterBean;
this.pageDetails = pageDetails == null ? new CannedQueryPageDetails() : pageDetails;
this.sortDetails = sortDetails == null ? new CannedQuerySortDetails() : sortDetails;
this.totalResultCountMax = totalResultCountMax;
this.queryExecutionId = queryExecutionId;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("NamedQueryParameters ")
.append("[parameterBean=").append(parameterBean)
.append(", pageDetails=").append(pageDetails)
.append(", sortDetails=").append(sortDetails)
.append(", requestTotalResultCountMax=").append(totalResultCountMax)
.append(", queryExecutionId=").append(queryExecutionId)
.append("]");
return sb.toString();
}
public String getQueryExecutionId()
{
return queryExecutionId;
}
/**
* @return the sort details (never <tt>null</tt>)
*/
public CannedQuerySortDetails getSortDetails()
{
return sortDetails;
}
/**
* @return the query paging details (never <tt>null</tt>)
*/
public CannedQueryPageDetails getPageDetails()
{
return pageDetails;
}
/**
* @return if > 0 then the query should not only return the required rows but should
* also return the total count (number of possible rows) up to the given max
* if 0 then query does not need to return the total count
*/
public int getTotalResultCountMax()
{
return totalResultCountMax;
}
/**
* Helper method to get the total number of query results that need to be obtained in order
* to satisfy the {@link #getPageDetails() paging requirements}, the
* maximum result count ... and an extra to provide
* 'hasMore' functionality.
*
* @return the minimum number of results required before pages can be created
*/
public int getResultsRequired()
{
int resultsForPaging = pageDetails.getResultsRequiredForPaging();
if (resultsForPaging < Integer.MAX_VALUE) // Add one for 'hasMore'
{
resultsForPaging++;
}
int maxRequired = Math.max(totalResultCountMax, resultsForPaging);
return maxRequired;
}
/**
* @return parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
*/
public Object getParameterBean()
{
return parameterBean;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.List;
/**
* Interface for results returned by {@link CannedQuery canned queries}.
*
* @author Derek Hulley, janv
* @since 4.0
*/
public interface CannedQueryResults<R> extends PagingResults<R>
{
/**
* Get the instance of the query that generated these results.
*
* @return the query that generated these results.
*/
CannedQuery<R> getOriginatingQuery();
/**
* Get the total number of results available within the pages of this result.
* The count excludes results chopped out by the paging process i.e. it is only
* the count of results physically obtainable through this instance.
*
* @return number of results available in the pages
*/
int getPagedResultCount();
/**
* Get the number of pages available
*
* @return the number of pages available
*/
int getPageCount();
/**
* Get a single result if there is only one result expected.
*
* @return a single result
* @throws IllegalStateException if the query returned more than one result
*/
R getSingleResult();
/**
* Get the paged results
*
* @return a list of paged results
*/
List<List<R>> getPages();
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* Details for canned queries supporting sorted results
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQuerySortDetails
{
/**
* Sort ordering for the sort pairs.
* @author Derek Hulley
* @since 4.0
*/
public static enum SortOrder
{
ASCENDING,
DESCENDING
}
private final List<Pair<? extends Object, SortOrder>> sortPairs;
/**
* Construct the sort details with a variable number of sort pairs.
* <p/>
* Sorting is done by:<br/>
* <b>key:</b> the key type to sort on<br/>
* <b>sortOrder:</b> the ordering of values associated with the key<br/>
*
* @param sortPairs the sort pairs, which will be applied in order
*/
public CannedQuerySortDetails(Pair<? extends Object, SortOrder> ... sortPairs)
{
this.sortPairs = Collections.unmodifiableList(Arrays.asList(sortPairs));
}
/**
* Construct the sort details from a list of sort pairs.
* <p/>
* Sorting is done by:<br/>
* <b>key:</b> the key type to sort on<br/>
* <b>sortOrder:</b> the ordering of values associated with the key<br/>
*
* @param sortPairs the sort pairs, which will be applied in order
*/
public CannedQuerySortDetails(List<Pair<? extends Object, SortOrder>> sortPairs)
{
this.sortPairs = Collections.unmodifiableList(sortPairs);
}
@Override
public String toString()
{
return "CannedQuerySortDetails [sortPairs=" + sortPairs + "]";
}
/**
* Get the sort definitions. The instance will become unmodifiable after this has been called.
*/
public List<Pair<? extends Object, SortOrder>> getSortPairs()
{
return Collections.unmodifiableList(sortPairs);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* An always empty {@link CannedQueryResults}, used when you know
* you can short circuit a query when no results are found.
*
* @author Nick Burch
* @since 4.0
*/
public class EmptyCannedQueryResults<R> extends EmptyPagingResults<R> implements CannedQueryResults<R>
{
private CannedQuery<R> query;
public EmptyCannedQueryResults(CannedQuery<R> query)
{
this.query = query;
}
@Override
public CannedQuery<R> getOriginatingQuery() {
return query;
}
@Override
public int getPageCount() {
return 0;
}
@Override
public int getPagedResultCount() {
return 0;
}
@Override
public List<List<R>> getPages() {
return Collections.emptyList();
}
@Override
public R getSingleResult() {
return null;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* An always empty {@link PagingResults}, used when you know
* you can short circuit a query when no results are found.
*
* @author Nick Burch
* @since 4.0
*/
public class EmptyPagingResults<R> implements PagingResults<R>
{
/**
* Returns an empty page
*/
public List<R> getPage()
{
return Collections.emptyList();
}
/**
* No more items remain
*/
public boolean hasMoreItems()
{
return false;
}
/**
* There are no results
*/
public Pair<Integer, Integer> getTotalResultCount()
{
return new Pair<Integer,Integer>(0,0);
}
/**
* There is no unique query ID, as no query was done
*/
public String getQueryExecutionId()
{
return null;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* Wraps a list of items as a {@link PagingResults}, used typically when
* migrating from a full listing system to a paged one.
*
* @author Nick Burch
* @since Odin
*/
public class ListBackedPagingResults<R> implements PagingResults<R>
{
private List<R> results;
private int size;
private boolean hasMore;
public ListBackedPagingResults(List<R> list)
{
this.results = Collections.unmodifiableList(list);
// No more items remain, the page is everything
size = list.size();
hasMore = false;
}
public ListBackedPagingResults(List<R> list, PagingRequest paging)
{
// Excerpt
int start = paging.getSkipCount();
int end = Math.min(list.size(), start + paging.getMaxItems());
if (paging.getMaxItems() == 0)
{
end = list.size();
}
this.results = Collections.unmodifiableList(
list.subList(start, end));
this.size = list.size();
this.hasMore = ! (list.size() == end);
}
/**
* Returns the whole set of results as one page
*/
public List<R> getPage()
{
return results;
}
public boolean hasMoreItems()
{
return hasMore;
}
/**
* We know exactly how many results there are
*/
public Pair<Integer, Integer> getTotalResultCount()
{
return new Pair<Integer,Integer>(size, size);
}
/**
* There is no unique query ID, as no query was done
*/
public String getQueryExecutionId()
{
return null;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Stores paging details based on a PagingRequest.
*
* @author steveglover
*
*/
public class PageDetails
{
private boolean hasMoreItems = false;
private int pageSize;
private int skipCount;
private int maxItems;
private int end;
public PageDetails(int pageSize, boolean hasMoreItems, int skipCount, int maxItems, int end)
{
super();
this.hasMoreItems = hasMoreItems;
this.pageSize = pageSize;
this.skipCount = skipCount;
this.maxItems = maxItems;
this.end = end;
}
public int getSkipCount()
{
return skipCount;
}
public int getMaxItems()
{
return maxItems;
}
public int getEnd()
{
return end;
}
public boolean hasMoreItems()
{
return hasMoreItems;
}
public int getPageSize()
{
return pageSize;
}
public static PageDetails getPageDetails(PagingRequest pagingRequest, int totalSize)
{
int skipCount = pagingRequest.getSkipCount();
int maxItems = pagingRequest.getMaxItems();
int end = skipCount + maxItems;
int pageSize = -1;
if(end < 0 || end > totalSize)
{
// overflow or greater than the total
end = totalSize;
pageSize = end - skipCount;
}
else
{
pageSize = maxItems;
}
if(pageSize < 0)
{
pageSize = 0;
}
boolean hasMoreItems = end < totalSize;
return new PageDetails(pageSize, hasMoreItems, skipCount, maxItems, end);
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import org.alfresco.api.AlfrescoPublicApi;
/**
* Simple wrapper for single page request (with optional request for total count up to a given max)
*
* @author janv
* @since 4.0
*/
@AlfrescoPublicApi
public class PagingRequest
{
private int skipCount = CannedQueryPageDetails.DEFAULT_SKIP_RESULTS;
private int maxItems;
private int requestTotalCountMax = 0; // request total count up to a given max (0 => do not request total count)
private String queryExecutionId;
/**
* Construct a page request
*
* @param maxItems the maximum number of items per page
*/
public PagingRequest(int maxItems)
{
this.maxItems = maxItems;
}
/**
* Construct a page request
*
* @param maxItems the maximum number of items per page
* @param skipCount the number of items to skip before the first page
*/
public PagingRequest(int skipCount, int maxItems)
{
this.skipCount = skipCount;
this.maxItems = maxItems;
}
/**
* Construct a page request
*
* @param maxItems the maximum number of items per page
* @param queryExecutionId a query execution ID associated with ealier paged requests
*/
public PagingRequest(int maxItems, String queryExecutionId)
{
setMaxItems(maxItems);
this.queryExecutionId = queryExecutionId;
}
/**
* Construct a page request
*
* @param skipCount the number of items to skip before the first page
* @param maxItems the maximum number of items per page
* @param queryExecutionId a query execution ID associated with ealier paged requests
*/
public PagingRequest(int skipCount, int maxItems, String queryExecutionId)
{
setSkipCount(skipCount);
setMaxItems(maxItems);
this.queryExecutionId = queryExecutionId;
}
/**
* Results to skip before retrieving the page. Usually a multiple of page size (ie. page size * num pages to skip).
* Default is 0.
*
* @return the number of results to skip before the page
*/
public int getSkipCount()
{
return skipCount;
}
/**
* Change the skip count. Must be called before the paging query is run.
*/
protected void setSkipCount(int skipCount)
{
this.skipCount = (skipCount < 0 ? CannedQueryPageDetails.DEFAULT_SKIP_RESULTS : skipCount);
}
/**
* Size of the page - if skip count is 0 then return up to max items.
*
* @return the maximum size of the page
*/
public int getMaxItems()
{
return maxItems;
}
/**
* Change the size of the page. Must be called before the paging query is run.
*/
protected void setMaxItems(int maxItems)
{
this.maxItems = (maxItems < 0 ? CannedQueryPageDetails.DEFAULT_PAGE_SIZE : maxItems);
}
/**
* Get requested total count (up to a given maximum).
*/
public int getRequestTotalCountMax()
{
return requestTotalCountMax;
}
/**
* Set request total count (up to a given maximum). Default is 0 => do not request total count (which allows possible query optimisation).
*
* @param requestTotalCountMax
*/
public void setRequestTotalCountMax(int requestTotalCountMax)
{
this.requestTotalCountMax = requestTotalCountMax;
}
/**
* Get a unique ID associated with these query results. This must be available before and
* after execution i.e. it must depend on the type of query and the query parameters
* rather than the execution results. Client has the option to pass this back as a hint when
* paging.
*
* @return a unique ID associated with the query execution results
*/
public String getQueryExecutionId()
{
return queryExecutionId;
}
/**
* Change the unique query ID for the results. Must be called before the paging query is run.
*/
protected void setQueryExecutionId(String queryExecutionId)
{
this.queryExecutionId = queryExecutionId;
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.List;
import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.util.Pair;
/**
* Marker interface for single page of results
*
* @author janv
* @since 4.0
*/
@AlfrescoPublicApi
public interface PagingResults<R>
{
/**
* Get the page of results.
*
* @return the results - possibly empty but never <tt>null</tt>
*/
public List<R> getPage();
/**
* True if more items on next page.
* <p/>
* Note: could also return true if page was cutoff/trimmed for some reason
* (eg. due to permission checks of large page of requested max items)
*
* @return true if more items (eg. on next page)<br/>
* - true => at least one more page (or incomplete page - if cutoff)<br/>
* - false => last page (or incomplete page - if cutoff)
*/
public boolean hasMoreItems();
/**
* Get the total result count assuming no paging applied. This value will only be available if
* the query supports it and the client requested it. By default, it is not requested.
* <p/>
* Returns result as an approx "range" pair <lower, upper>
* <ul>
* <li>null (or lower is null): unknown total count (or not requested by the client).</li>
* <li>lower = upper : total count should be accurate</li>
* <li>lower < upper : total count is an approximation ("about") - somewhere in the given range (inclusive)</li>
* <li>upper is null : total count is "more than" lower (upper is unknown)</li>
* </ul>
*
* @return Returns the total results as a range (all results, including the paged results returned)
*/
public Pair<Integer, Integer> getTotalResultCount();
/**
* Get a unique ID associated with these query results. This must be available before and
* after execution i.e. it must depend on the type of query and the query parameters
* rather than the execution results. Client has the option to pass this back as a hint when
* paging.
*
* @return a unique ID associated with the query execution results
*/
public String getQueryExecutionId();
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Marker interface to show that permissions have already been applied to the results (and possibly cutoff)
*
* @author janv
* @since 4.0
*/
public interface PermissionedResults
{
/**
* @return <tt>true</tt> - if permissions have been applied to the results
*/
public boolean permissionsApplied();
/**
* @return <tt>true</tt> - if permission checks caused results to be cutoff (either due to max count or max time)
*/
public boolean hasMoreItems();
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.scripts;
import org.alfresco.error.AlfrescoRuntimeException;
/**
* @author Kevin Roast
*/
public class ScriptException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = 1739480648583299623L;
/**
* @param msgId String
*/
public ScriptException(String msgId)
{
super(msgId);
}
/**
* @param msgId String
* @param cause Throwable
*/
public ScriptException(String msgId, Throwable cause)
{
super(msgId, cause);
}
/**
* @param msgId String
* @param params Object[]
*/
public ScriptException(String msgId, Object[] params)
{
super(msgId, params);
}
/**
* @param msgId String
* @param msgParams Object[]
* @param cause Throwable
*/
public ScriptException(String msgId, Object[] msgParams, Throwable cause)
{
super(msgId, msgParams, cause);
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.scripts;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
/**
* @author Kevin Roast
*/
public class ScriptResourceHelper
{
private static final String SCRIPT_ROOT = "_root";
private static final String IMPORT_PREFIX = "<import";
private static final String IMPORT_RESOURCE = "resource=\"";
/**
* Resolve the import directives in the specified script. The implementation of the supplied
* ScriptResourceLoader instance is responsible for handling the resource retrieval.
* <p>
* Multiple includes of the same resource are dealt with correctly and nested includes of scripts
* is fully supported.
* <p>
* Note that for performance reasons the script import directive syntax and placement in the file
* is very strict. The import lines <i>must</i> always be first in the file - even before any comments.
* Immediately that the script service detects a non-import line it will assume the rest of the
* file is executable script and no longer attempt to search for any further import directives. Therefore
* all imports should be at the top of the script, one following the other, in the correct syntax and
* with no comments present - the only separators valid between import directives is white space.
*
* @param script The script content to resolve imports in
*
* @return a valid script with all nested includes resolved into a single script instance
*/
public static String resolveScriptImports(String script, ScriptResourceLoader loader, Log logger)
{
// use a linked hashmap to preserve order of includes - the key in the collection is used
// to resolve multiple includes of the same scripts and therefore cyclic includes also
Map<String, String> scriptlets = new LinkedHashMap<String, String>(8, 1.0f);
// perform a recursive resolve of all script imports
recurseScriptImports(SCRIPT_ROOT, script, loader, scriptlets, logger);
if (scriptlets.size() == 1)
{
// quick exit for single script with no includes
if (logger.isTraceEnabled())
logger.trace("Script content resolved to:\r\n" + script);
return script;
}
else
{
// calculate total size of buffer required for the script and all includes
int length = 0;
for (String scriptlet : scriptlets.values())
{
length += scriptlet.length();
}
// append the scripts together to make a single script
StringBuilder result = new StringBuilder(length);
for (String scriptlet : scriptlets.values())
{
result.append(scriptlet);
}
if (logger.isTraceEnabled())
logger.trace("Script content resolved to:\r\n" + result.toString());
return result.toString();
}
}
/**
* Recursively resolve imports in the specified scripts, adding the imports to the
* specific list of scriplets to combine later.
*
* @param location Script location - used to ensure duplicates are not added
* @param script The script to recursively resolve imports for
* @param scripts The collection of scriplets to execute with imports resolved and removed
*/
private static void recurseScriptImports(
String location, String script, ScriptResourceLoader loader, Map<String, String> scripts, Log logger)
{
int index = 0;
// skip any initial whitespace
for (; index<script.length(); index++)
{
if (Character.isWhitespace(script.charAt(index)) == false)
{
break;
}
}
// look for the "<import" directive marker
if (script.startsWith(IMPORT_PREFIX, index))
{
// skip whitespace between "<import" and "resource"
boolean afterWhitespace = false;
index += IMPORT_PREFIX.length() + 1;
for (; index<script.length(); index++)
{
if (Character.isWhitespace(script.charAt(index)) == false)
{
afterWhitespace = true;
break;
}
}
if (afterWhitespace == true && script.startsWith(IMPORT_RESOURCE, index))
{
// found an import line!
index += IMPORT_RESOURCE.length();
int resourceStart = index;
for (; index<script.length(); index++)
{
if (script.charAt(index) == '"' && script.charAt(index + 1) == '>')
{
// found end of import line - so we have a resource path
String resource = script.substring(resourceStart, index);
if (logger.isDebugEnabled())
logger.debug("Found script resource import: " + resource);
if (scripts.containsKey(resource) == false)
{
// load the script resource (and parse any recursive includes...)
String includedScript = loader.loadScriptResource(resource);
if (includedScript != null)
{
if (logger.isDebugEnabled())
logger.debug("Succesfully located script '" + resource + "'");
recurseScriptImports(resource, includedScript, loader, scripts, logger);
}
}
else
{
if (logger.isDebugEnabled())
logger.debug("Note: already imported resource: " + resource);
}
// continue scanning this script for additional includes
// skip the last two characters of the import directive
for (index += 2; index<script.length(); index++)
{
if (Character.isWhitespace(script.charAt(index)) == false)
{
break;
}
}
recurseScriptImports(location, script.substring(index), loader, scripts, logger);
return;
}
}
// if we get here, we failed to find the end of an import line
throw new ScriptException(
"Malformed 'import' line - must be first in file, no comments and strictly of the form:" +
"\r\n<import resource=\"...\">");
}
else
{
throw new ScriptException(
"Malformed 'import' line - must be first in file, no comments and strictly of the form:" +
"\r\n<import resource=\"...\">");
}
}
else
{
// no (further) includes found - include the original script content
if (logger.isDebugEnabled())
logger.debug("Imports resolved, adding resource '" + location);
if (logger.isTraceEnabled())
logger.trace(script);
scripts.put(location, script);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.scripts;
/**
* @author Kevin Roast
*/
public interface ScriptResourceLoader
{
public String loadScriptResource(String resource);
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.util.HashMap;
import java.util.Map;
/**
* Utility class to assist in extracting program arguments.
*
* @author Derek Hulley
* @since V2.1-A
*/
public class ArgumentHelper
{
private String usage;
private Map<String, String> args;
public static Map<String, String> ripArgs(String ... args)
{
Map<String, String> argsMap = new HashMap<String, String>(5);
for (String arg : args)
{
int index = arg.indexOf('=');
if (!arg.startsWith("--") || index < 0 || index == arg.length() - 1)
{
// Ignore it
continue;
}
String name = arg.substring(2, index);
String value = arg.substring(index + 1, arg.length());
argsMap.put(name, value);
}
return argsMap;
}
public ArgumentHelper(String usage, String[] args)
{
this.usage = usage;
this.args = ArgumentHelper.ripArgs(args);
}
/**
* @throws IllegalArgumentException if the argument doesn't match the requirements.
*/
public String getStringValue(String arg, boolean mandatory, boolean nonEmpty)
{
String value = args.get(arg);
if (value == null && mandatory)
{
throw new IllegalArgumentException("Argument '" + arg + "' is required.");
}
else if (value != null && value.length() == 0 && nonEmpty)
{
throw new IllegalArgumentException("Argument '" + arg + "' may not be empty.");
}
return value;
}
/**
* @return Returns the value assigned or the minimum value if the parameter was not present
* @throws IllegalArgumentException if the argument doesn't match the requirements.
*/
public int getIntegerValue(String arg, boolean mandatory, int minValue, int maxValue)
{
String valueStr = args.get(arg);
if (valueStr == null)
{
if (mandatory)
{
throw new IllegalArgumentException("Argument '" + arg + "' is required.");
}
else
{
return minValue;
}
}
// Now convert
try
{
int value = Integer.parseInt(valueStr);
if (value < minValue || value > maxValue)
{
throw new IllegalArgumentException("Argument '" + arg + "' must be in range " + minValue + " to " + maxValue + ".");
}
return value;
}
catch (NumberFormatException e)
{
throw new IllegalArgumentException("Argument '" + arg + "' must be a valid integer.");
}
}
public void printUsage()
{
System.out.println(usage);
}
}

View File

@@ -0,0 +1,445 @@
/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Generic bridge table support with optional reference counting to allow multiple membership for an object via several
* relationships.
*
* @author Andy
*/
public class BridgeTable<T>
{
HashMap<T, HashMap<Integer, HashMap<T, Counter>>> descendants = new HashMap<T, HashMap<Integer, HashMap<T, Counter>>>();
HashMap<T, HashMap<Integer, HashMap<T, Counter>>> ancestors = new HashMap<T, HashMap<Integer, HashMap<T, Counter>>>();
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void addLink(T parent, T child)
{
readWriteLock.writeLock().lock();
try
{
addDescendants(parent, child);
addAncestors(parent, child);
}
finally
{
readWriteLock.writeLock().unlock();
}
}
public void addLink(Pair<T, T> link)
{
addLink(link.getFirst(), link.getSecond());
}
public void addLinks(Collection<Pair<T, T>> links)
{
for (Pair<T, T> link : links)
{
addLink(link);
}
}
public void removeLink(T parent, T child)
{
readWriteLock.writeLock().lock();
try
{
removeDescendants(parent, child);
removeAncestors(parent, child);
}
finally
{
readWriteLock.writeLock().unlock();
}
}
public void removeLink(Pair<T, T> link)
{
removeLink(link.getFirst(), link.getSecond());
}
public void removeLinks(Collection<Pair<T, T>> links)
{
for (Pair<T, T> link : links)
{
removeLink(link);
}
}
public HashSet<T> getDescendants(T node)
{
return getDescendants(node, 1, Integer.MAX_VALUE);
}
public HashSet<T> getDescendants(T node, int position)
{
return getDescendants(node, position, position);
}
public HashSet<T> getDescendants(T node, int start, int end)
{
HashSet<T> answer = new HashSet<T>();
HashMap<Integer, HashMap<T, Counter>> found = descendants.get(node);
if (found != null)
{
for (Integer key : found.keySet())
{
if ((key.intValue() >= start) && (key.intValue() <= end))
{
HashMap<T, Counter> asd = found.get(key);
answer.addAll(asd.keySet());
}
}
}
return answer;
}
public HashSet<T> getAncestors(T node)
{
return getAncestors(node, 1, Integer.MAX_VALUE);
}
public HashSet<T> getAncestors(T node, int position)
{
return getAncestors(node, position, position);
}
public HashSet<T> getAncestors(T node, int start, int end)
{
HashSet<T> answer = new HashSet<T>();
HashMap<Integer, HashMap<T, Counter>> found = ancestors.get(node);
if (found != null)
{
for (Integer key : found.keySet())
{
if ((key.intValue() >= start) && (key.intValue() <= end))
{
HashMap<T, Counter> asd = found.get(key);
answer.addAll(asd.keySet());
}
}
}
return answer;
}
/**
* @param parent T
* @param child T
*/
private void addDescendants(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> parentsDescendants = descendants.get(parent);
if (parentsDescendants == null)
{
parentsDescendants = new HashMap<Integer, HashMap<T, Counter>>();
descendants.put(parent, parentsDescendants);
}
HashMap<Integer, HashMap<T, Counter>> childDescendantsToAdd = descendants.get(child);
// add all the childs children to the parents descendants
add(childDescendantsToAdd, Integer.valueOf(0), parentsDescendants, child);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> ancestorsToFixUp = ancestors.get(parent);
if (ancestorsToFixUp != null)
{
for (Integer ancestorPosition : ancestorsToFixUp.keySet())
{
HashMap<T, Counter> ancestorsToFixUpAtPosition = ancestorsToFixUp.get(ancestorPosition);
for (T ancestorToFixUpAtPosition : ancestorsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> ancestorDescendants = descendants.get(ancestorToFixUpAtPosition);
add(childDescendantsToAdd, ancestorPosition, ancestorDescendants, child);
}
}
}
}
/**
* @param parent T
* @param child T
*/
private void removeDescendants(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> parentsDescendants = descendants.get(parent);
if (parentsDescendants == null)
{
return;
}
HashMap<Integer, HashMap<T, Counter>> childDescendantsToRemove = descendants.get(child);
// add all the childs children to the parents descendants
remove(childDescendantsToRemove, Integer.valueOf(0), parentsDescendants, child);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> ancestorsToFixUp = ancestors.get(parent);
if (ancestorsToFixUp != null)
{
for (Integer ancestorPosition : ancestorsToFixUp.keySet())
{
HashMap<T, Counter> ancestorsToFixUpAtPosition = ancestorsToFixUp.get(ancestorPosition);
for (T ancestorToFixUpAtPosition : ancestorsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> ancestorDescendants = descendants.get(ancestorToFixUpAtPosition);
remove(childDescendantsToRemove, ancestorPosition, ancestorDescendants, child);
}
}
}
}
/**
* @param parent T
* @param child T
*/
private void removeAncestors(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> childsAncestors = ancestors.get(child);
if (childsAncestors == null)
{
return;
}
HashMap<Integer, HashMap<T, Counter>> parentAncestorsToRemove = ancestors.get(parent);
// add all the childs children to the parents descendants
remove(parentAncestorsToRemove, Integer.valueOf(0), childsAncestors, parent);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> decendantsToFixUp = descendants.get(child);
if (decendantsToFixUp != null)
{
for (Integer descendantPosition : decendantsToFixUp.keySet())
{
HashMap<T, Counter> decendantsToFixUpAtPosition = decendantsToFixUp.get(descendantPosition);
for (T descendantToFixUpAtPosition : decendantsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> descendantAncestors = ancestors.get(descendantToFixUpAtPosition);
remove(parentAncestorsToRemove, descendantPosition, descendantAncestors, parent);
}
}
}
}
/**
* @param toAdd HashMap<Integer, HashMap<T, Counter>>
* @param position Integer
* @param target HashMap<Integer, HashMap<T, Counter>>
* @param node T
*/
private void add(HashMap<Integer, HashMap<T, Counter>> toAdd, Integer position, HashMap<Integer, HashMap<T, Counter>> target, T node)
{
// add direct child
Integer directKey = Integer.valueOf(position.intValue() + 1);
HashMap<T, Counter> direct = target.get(directKey);
if (direct == null)
{
direct = new HashMap<T, Counter>();
target.put(directKey, direct);
}
Counter counter = direct.get(node);
if (counter == null)
{
counter = new Counter();
direct.put(node, counter);
}
counter.increment();
if (toAdd != null)
{
for (Integer depth : toAdd.keySet())
{
Integer newKey = Integer.valueOf(position.intValue() + depth.intValue() + 1);
HashMap<T, Counter> toAddAtDepth = toAdd.get(depth);
HashMap<T, Counter> targetAtDepthPlusOne = target.get(newKey);
if (targetAtDepthPlusOne == null)
{
targetAtDepthPlusOne = new HashMap<T, Counter>();
target.put(newKey, targetAtDepthPlusOne);
}
for (T key : toAddAtDepth.keySet())
{
Counter counterToAdd = toAddAtDepth.get(key);
Counter counterToAddTo = targetAtDepthPlusOne.get(key);
if (counterToAddTo == null)
{
counterToAddTo = new Counter();
targetAtDepthPlusOne.put(key, counterToAddTo);
}
counterToAddTo.add(counterToAdd);
}
}
}
}
/**
* @param toRemove HashMap<Integer, HashMap<T, Counter>>
* @param position Integer
* @param target HashMap<Integer, HashMap<T, Counter>>
* @param node T
*/
private void remove(HashMap<Integer, HashMap<T, Counter>> toRemove, Integer position, HashMap<Integer, HashMap<T, Counter>> target, T node)
{
// remove direct child
Integer directKey = Integer.valueOf(position.intValue() + 1);
HashMap<T, Counter> direct = target.get(directKey);
if (direct != null)
{
Counter counter = direct.get(node);
if (counter != null)
{
counter.decrement();
if (counter.getCount() == 0)
{
direct.remove(node);
}
}
}
if (toRemove != null)
{
for (Integer depth : toRemove.keySet())
{
Integer newKey = Integer.valueOf(position.intValue() + depth.intValue() + 1);
HashMap<T, Counter> toRemoveAtDepth = toRemove.get(depth);
HashMap<T, Counter> targetAtDepthPlusOne = target.get(newKey);
if (targetAtDepthPlusOne != null)
{
for (T key : toRemoveAtDepth.keySet())
{
Counter counterToRemove = toRemoveAtDepth.get(key);
Counter counterToRemoveFrom = targetAtDepthPlusOne.get(key);
if (counterToRemoveFrom != null)
{
counterToRemoveFrom.remove(counterToRemove);
if (counterToRemoveFrom.getCount() == 0)
{
targetAtDepthPlusOne.remove(key);
}
}
}
}
}
}
}
/**
* @param parent T
* @param child T
*/
private void addAncestors(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> childsAncestors = ancestors.get(child);
if (childsAncestors == null)
{
childsAncestors = new HashMap<Integer, HashMap<T, Counter>>();
ancestors.put(child, childsAncestors);
}
HashMap<Integer, HashMap<T, Counter>> parentAncestorsToAdd = ancestors.get(parent);
// add all the childs children to the parents descendants
add(parentAncestorsToAdd, Integer.valueOf(0), childsAncestors, parent);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> descenantsToFixUp = descendants.get(child);
if (descenantsToFixUp != null)
{
for (Integer descendantPosition : descenantsToFixUp.keySet())
{
HashMap<T, Counter> descenantsToFixUpAtPosition = descenantsToFixUp.get(descendantPosition);
for (T descenantToFixUpAtPosition : descenantsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> descendatAncestors = ancestors.get(descenantToFixUpAtPosition);
add(parentAncestorsToAdd, descendantPosition, descendatAncestors, parent);
}
}
}
}
public int size()
{
return ancestors.size();
}
private static class Counter
{
int count = 0;
void increment()
{
count++;
}
void decrement()
{
count--;
}
int getCount()
{
return count;
}
void add(Counter other)
{
count += other.count;
}
void remove(Counter other)
{
count -= other.count;
}
}
/**
* @return Set<T>
*/
public Set<T> keySet()
{
return ancestors.keySet();
}
}

View File

@@ -0,0 +1,419 @@
/*
* Copyright (C) 2005-2018 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import static java.util.Arrays.stream;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
/**
* Provides <b>thread safe</b> means of obtaining a cached date formatter.
* <p>
* The cached string-date mappings are stored in a <tt>WeakHashMap</tt>.
*
* @see java.text.DateFormat#setLenient(boolean)
*
* @author Derek Hulley
* @author Andrea Gazzarini
*/
public class CachingDateFormat extends SimpleDateFormat
{
private static final long serialVersionUID = 3258415049197565235L;
public static final String UTC = "UTC";
public static final String FORMAT_FULL_GENERIC = "yyyy-MM-dd'T'HH:mm:ss";
public static final String FORMAT_CMIS_SQL = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public static final String FORMAT_SOLR = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
public static final String UTC_WITHOUT_MSECS = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public static final String FORMAT_DATE_GENERIC = "yyyy-MM-dd";
public static final String FORMAT_TIME_GENERIC = "HH:mm:ss";
public static final StringAndResolution[] LENIENT_FORMATS =
{
new StringAndResolution("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Calendar.MILLISECOND),
new StringAndResolution("yyyy-MM-dd'T'HH:mm:ss.SSS", Calendar.MILLISECOND),
new StringAndResolution("yyyy-MM-dd'T'HH:mm:ssZ", Calendar.SECOND),
new StringAndResolution("yyyy-MM-dd'T'HH:mm:ss", Calendar.SECOND),
new StringAndResolution("yyyy-MM-dd'T'HH:mmZ", Calendar.MINUTE),
new StringAndResolution("yyyy-MM-dd'T'HH:mm", Calendar.MINUTE),
new StringAndResolution("yyyy-MM-dd'T'HHZ", Calendar.HOUR_OF_DAY),
new StringAndResolution("yyyy-MM-dd'T'HH", Calendar.HOUR_OF_DAY),
new StringAndResolution("yyyy-MM-dd'T'Z", Calendar.DAY_OF_MONTH),
new StringAndResolution("yyyy-MM-dd'T'", Calendar.DAY_OF_MONTH),
new StringAndResolution("yyyy-MM-ddZ", Calendar.DAY_OF_MONTH),
new StringAndResolution("yyyy-MM-dd", Calendar.DAY_OF_MONTH),
new StringAndResolution("yyyy-MMZ", Calendar.MONTH),
new StringAndResolution("yyyy-MM", Calendar.MONTH),
new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ss.SSSZ", Calendar.MILLISECOND),
new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ss.SSS", Calendar.MILLISECOND),
new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ssZ", Calendar.SECOND),
new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ss", Calendar.SECOND),
new StringAndResolution( "yyyy-MMM-dd'T'HH:mmZ", Calendar.MINUTE),
new StringAndResolution( "yyyy-MMM-dd'T'HH:mm", Calendar.MINUTE),
new StringAndResolution( "yyyy-MMM-dd'T'HHZ", Calendar.HOUR_OF_DAY),
new StringAndResolution( "yyyy-MMM-dd'T'HH", Calendar.HOUR_OF_DAY),
new StringAndResolution( "yyyy-MMM-dd'T'Z",Calendar.DAY_OF_MONTH),
new StringAndResolution( "yyyy-MMM-dd'T'",Calendar.DAY_OF_MONTH),
new StringAndResolution( "yyyy-MMM-ddZ", Calendar.DAY_OF_MONTH),
new StringAndResolution( "yyyy-MMM-dd", Calendar.DAY_OF_MONTH),
new StringAndResolution( "yyyy-MMMZ", Calendar.MONTH),
new StringAndResolution( "yyyy-MMM", Calendar.MONTH),
new StringAndResolution("yyyyZ", Calendar.YEAR),
new StringAndResolution("yyyy", Calendar.YEAR)
};
static ThreadLocal<SimpleDateFormat> S_LOCAL_DATE_FORMAT = ThreadLocal.withInitial(() -> newDateFormat(FORMAT_FULL_GENERIC));
static ThreadLocal<SimpleDateFormat> S_LOCAL_DATEONLY_FORMAT = ThreadLocal.withInitial(() -> newDateFormat(FORMAT_DATE_GENERIC));
static ThreadLocal<SimpleDateFormat> S_LOCAL_TIMEONLY_FORMAT = ThreadLocal.withInitial(() -> newDateFormat(FORMAT_TIME_GENERIC));
static ThreadLocal<SimpleDateFormat> S_LOCAL_CMIS_SQL_DATETIME = ThreadLocal.withInitial(() -> newDateFormat(FORMAT_CMIS_SQL));
static ThreadLocal<SimpleDateFormat> S_LOCAL_SOLR_DATETIME = ThreadLocal.withInitial(()->
{
CachingDateFormat formatter = newDateFormatWithLocale(FORMAT_SOLR, Locale.ENGLISH);
/*
SEARCH-1263
Apache Solr only supports the ISO 8601 date format:
UTC and western locale are mandatory (only Arabic numerals (0123456789) are supported)
*/
formatter.setTimeZone(TimeZone.getTimeZone(UTC));
return formatter;
});
static ThreadLocal<SimpleDateFormat> S_UTC_DATETIME_WITHOUT_MSECS = ThreadLocal.withInitial(() ->
{
CachingDateFormat formatter = newDateFormatWithLocale(UTC_WITHOUT_MSECS, Locale.ENGLISH);
formatter.setTimeZone(TimeZone.getTimeZone(UTC));
return formatter;
});
static ThreadLocal<SimpleDateFormatAndResolution[]> S_LENIENT_PARSERS =
ThreadLocal.withInitial(() ->
stream(LENIENT_FORMATS)
.map(format -> {
CachingDateFormat formatter = new CachingDateFormat(format.string);
formatter.setLenient(false);
return new SimpleDateFormatAndResolution(formatter, format.resolution); })
.toArray(SimpleDateFormatAndResolution[]::new));
private Map<String, Date> cacheDates = new WeakHashMap<>(89);
private CachingDateFormat(String pattern, Locale locale)
{
super(pattern, locale);
}
private CachingDateFormat(String pattern)
{
super(pattern);
}
@Override
public String toString()
{
return this.toPattern();
}
/**
* @param length
* the type of date format, e.g. {@link CachingDateFormat#LONG }
* @param locale
* the <code>Locale</code> that will be used to determine the
* date pattern
*
* @see #getDateFormat(String, boolean)
* @see CachingDateFormat#SHORT
* @see CachingDateFormat#MEDIUM
* @see CachingDateFormat#LONG
* @see CachingDateFormat#FULL
*/
public static SimpleDateFormat getDateFormat(int length, Locale locale, boolean lenient)
{
SimpleDateFormat dateFormat = (SimpleDateFormat) CachingDateFormat.getDateInstance(length, locale);
// extract the format string
String pattern = dateFormat.toPattern();
// we have a pattern to use
return getDateFormat(pattern, lenient);
}
/**
* @param dateLength
* the type of date format, e.g. {@link CachingDateFormat#LONG }
* @param timeLength
* the type of time format, e.g. {@link CachingDateFormat#LONG }
* @param locale
* the <code>Locale</code> that will be used to determine the
* date pattern
*
* @see #getDateFormat(String, boolean)
* @see CachingDateFormat#SHORT
* @see CachingDateFormat#MEDIUM
* @see CachingDateFormat#LONG
* @see CachingDateFormat#FULL
*/
public static SimpleDateFormat getDateTimeFormat(int dateLength, int timeLength, Locale locale, boolean lenient)
{
SimpleDateFormat dateFormat = (SimpleDateFormat) CachingDateFormat.getDateTimeInstance(dateLength, timeLength, locale);
// extract the format string
String pattern = dateFormat.toPattern();
// we have a pattern to use
return getDateFormat(pattern, lenient);
}
/**
* @param pattern
* the conversion pattern to use
* @param lenient
* true to allow the parser to extract the date in conceivable
* manner
* @return Returns a conversion-cacheing formatter for the given pattern,
* but the instance itself is not cached
*/
public static SimpleDateFormat getDateFormat(String pattern, boolean lenient)
{
// create an alfrescoDateFormat for cacheing purposes
SimpleDateFormat dateFormat = new CachingDateFormat(pattern);
// set leniency
dateFormat.setLenient(lenient);
// done
return dateFormat;
}
/**
* Returns a thread-safe formatter for the generic date/time format.
*
* @see #FORMAT_FULL_GENERIC
* @return a thread-safe formatter for the generic date/time format.
*/
public static SimpleDateFormat getDateFormat()
{
return S_LOCAL_DATE_FORMAT.get();
}
/**
* Returns a thread-safe formatter for the cmis sql datetime format.
*
* @see #FORMAT_CMIS_SQL
* @return a thread-safe formatter for the cmis sql datetime format.
*/
public static SimpleDateFormat getCmisSqlDatetimeFormat()
{
return S_LOCAL_CMIS_SQL_DATETIME.get();
}
/**
* Returns a thread-safe formatter for the Solr ISO 8601 datetime format (without the msecs part).
*
* @see #UTC_WITHOUT_MSECS
* @return Returns a thread-safe formatter for the Solr ISO 8601 datetime format (without the msecs part).
*/
public static SimpleDateFormat getSolrDatetimeFormatWithoutMsecs()
{
return S_UTC_DATETIME_WITHOUT_MSECS.get();
}
/**
* Returns a thread-safe formatter for the Solr ISO 8601 datetime format.
*
* @see #FORMAT_SOLR
* @return a thread-safe formatter for the Solr ISO 8601 datetime format
*/
public static SimpleDateFormat getSolrDatetimeFormat()
{
return S_LOCAL_SOLR_DATETIME.get();
}
/**
* @return Returns a thread-safe formatter for the generic date format
*
* @see #FORMAT_DATE_GENERIC
*/
public static SimpleDateFormat getDateOnlyFormat()
{
return S_LOCAL_DATEONLY_FORMAT.get();
}
/**
* Returns a thread-safe formatter for the generic time format.
*
* @see #FORMAT_TIME_GENERIC
* @return a thread-safe formatter for the generic time format.
*/
public static SimpleDateFormat getTimeOnlyFormat()
{
return S_LOCAL_TIMEONLY_FORMAT.get();
}
/**
* Parses and caches date strings.
*
* @see java.text.DateFormat#parse(java.lang.String,
* java.text.ParsePosition)
*/
public Date parse(String text, ParsePosition pos)
{
Date cached = cacheDates.get(text);
if (cached == null)
{
Date date = super.parse(text, pos);
if ((date != null) && (pos.getIndex() == text.length()))
{
cacheDates.put(text, date);
return (Date) date.clone();
}
else
{
return date;
}
}
else
{
pos.setIndex(text.length());
return (Date) cached.clone();
}
}
public static Pair<Date, Integer> lenientParse(String text, int minimumResolution) throws ParseException
{
DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
try
{
Date parsed = fmt.parseDateTime(text).toDate();
return new Pair<Date, Integer>(parsed, Calendar.MILLISECOND);
}
catch(IllegalArgumentException e)
{
// Nothing to be done here
}
SimpleDateFormatAndResolution[] formatters = getLenientFormatters();
for(SimpleDateFormatAndResolution formatter : formatters)
{
if(formatter.resolution >= minimumResolution)
{
ParsePosition pp = new ParsePosition(0);
Date parsed = formatter.simpleDateFormat.parse(text, pp);
if ((pp.getIndex() < text.length()) || (parsed == null))
{
continue;
}
return new Pair<Date, Integer>(parsed, formatter.resolution);
}
}
throw new ParseException("Unknown date format", 0);
}
public static SimpleDateFormatAndResolution[] getLenientFormatters()
{
return S_LENIENT_PARSERS.get();
}
public static class StringAndResolution
{
String string;
int resolution;
/**
* @return the resolution
*/
public int getResolution()
{
return resolution;
}
/**
* @param resolution the resolution to set
*/
public void setResolution(int resolution)
{
this.resolution = resolution;
}
StringAndResolution(String string, int resolution)
{
this.string = string;
this.resolution = resolution;
}
}
public static class SimpleDateFormatAndResolution
{
SimpleDateFormat simpleDateFormat;
int resolution;
SimpleDateFormatAndResolution(SimpleDateFormat simpleDateFormat, int resolution)
{
this.simpleDateFormat = simpleDateFormat;
this.resolution = resolution;
}
/**
* @return the simpleDateFormat
*/
public SimpleDateFormat getSimpleDateFormat()
{
return simpleDateFormat;
}
/**
* @return the resolution
*/
public int getResolution()
{
return resolution;
}
}
/**
* Creates a new non-lenient {@link CachingDateFormat} instance.
*
* @param pattern the date / datetime pattern.
* @return new non-lenient {@link CachingDateFormat} instance.
*/
private static CachingDateFormat newDateFormat(String pattern)
{
CachingDateFormat formatter = new CachingDateFormat(pattern);
formatter.setLenient(false);
return formatter;
}
/**
* Creates a new non-lenient localised {@link CachingDateFormat} instance.
*
* @param pattern the date / datetime pattern.
* @param locale the locale.
* @return new non-lenient {@link CachingDateFormat} instance.
*/
private static CachingDateFormat newDateFormatWithLocale(String pattern, Locale locale)
{
CachingDateFormat formatter = new CachingDateFormat(pattern, locale);
formatter.setLenient(false);
return formatter;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
/**
* Content
*
* @author dcaruana
*/
public interface Content
{
/**
* Gets content as a string
*
* @return content as a string
* @throws IOException
*/
public String getContent() throws IOException;
/**
* Gets the content mimetype
*
* @return mimetype
*/
public String getMimetype();
/**
* Gets the content encoding
*
* @return encoding
*/
public String getEncoding();
/**
* Gets the content length (in bytes)
*
* @return length
*/
public long getSize();
/**
* Gets the content input stream
*
* @return input stream
*/
public InputStream getInputStream();
/**
* Gets the content reader (which is sensitive to encoding)
*
* @return Reader
*/
public Reader getReader() throws IOException;
}

View File

@@ -0,0 +1,809 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.alfresco.encoding.CharactersetFinder;
import org.alfresco.encoding.GuessEncodingCharsetFinder;
import org.alfresco.util.exec.RuntimeExec;
import org.alfresco.util.exec.RuntimeExec.ExecutionResult;
/**
* Utility to convert text files.
* <p/>
* Check the usage options with the <b>--help</b> option.
* <p/>
* Here are some examples of how to use the <code>main</code> method:
* <ul>
* <li>
* <b>--help</b><br/>
* Produce the help output.
* </li>
* <li>
* <b>--dry-run --encoding=UTF-8 --line-ending=WINDOWS --match="(.java|.xml|.jsp|.properties)$" --ignore="(.svn|classes)" "w:\"</b><br/>
* Find all source (.java, .xml, .jsp and .properties) files in directory "w:\".<br/>
* List files and show which would change when converting to CR-LF (Windows) line endings.<br/>
* Where auto-detection of the file is ambiguous, assume UTF-8.
* </li>
* <li>
* <b>--encoding=UTF-8 --line-ending=WINDOWS --match="(.java|.xml|.jsp|.properties)$" --ignore="(.svn|classes)" "w:\"</b><br/>
* Find all source (.java, .xml, .jsp and .properties) files in directory "w:\". Recurse into subdirectories.<br/>
* Convert files, where necessary, to have CR-LF (Windows) line endings.<br/>
* Where auto-detection of the file encoding is ambiguous, assume UTF-8.<br/>
* Backups (.bak) files will be created.
* </li>
* <li>
* <b>--svn-update --no-backup --encoding=UTF-8 --line-ending=WINDOWS --match="(.java|.xml|.jsp|.properties)$" "w:\"</b><br/>
* Issue a 'svn status' command on directory "w:\" and match the regular expressions given to find files.<br/>
* Convert files, where necessary, to have CR-LF (Windows) line endings.<br/>
* Where auto-detection of the file encoding is ambiguous, assume UTF-8. Write out as UTF-8.<br/>
* No backups files will be created.
* </li>
* </ul>
*
* @author Derek Hulley
*/
public class Convert
{
private static final String OPTION_HELP = "--help";
private static final String OPTION_SVN_STATUS = "--svn-status";
private static final String OPTION_MATCH = "--match=";
private static final String OPTION_IGNORE = "--ignore=";
private static final String OPTION_ENCODING= "--encoding=";
private static final String OPTION_LINE_ENDING = "--line-ending=";
private static final String OPTION_REPLACE_TABS= "--replace-tabs=";
private static final String OPTION_NO_RECURSE = "--no-recurse";
private static final String OPTION_NO_BACKUP = "--no-backup";
private static final String OPTION_DRY_RUN = "--dry-run";
private static final String OPTION_VERBOSE = "--verbose";
private static final String OPTION_QUIET = "--quiet";
private static final Set<String> OPTIONS = new HashSet<String>(13);
static
{
OPTIONS.add(OPTION_HELP);
OPTIONS.add(OPTION_SVN_STATUS);
OPTIONS.add(OPTION_MATCH);
OPTIONS.add(OPTION_IGNORE);
OPTIONS.add(OPTION_ENCODING);
OPTIONS.add(OPTION_LINE_ENDING);
OPTIONS.add(OPTION_REPLACE_TABS);
OPTIONS.add(OPTION_NO_RECURSE);
OPTIONS.add(OPTION_NO_BACKUP);
OPTIONS.add(OPTION_DRY_RUN);
OPTIONS.add(OPTION_VERBOSE);
OPTIONS.add(OPTION_QUIET);
}
/**
* @see GuessEncodingCharsetFinder
*/
private static final CharactersetFinder CHARACTER_ENCODING_FINDER = new GuessEncodingCharsetFinder();
private File startDir = null;
private boolean svnStatus = false;
private boolean dryRun = false;
private Pattern matchPattern = null;
private Pattern ignorePattern = null;
private Charset charset = null;
private String lineEnding = null;
private Integer replaceTabs = null;
private boolean noRecurse = false;
private boolean noBackup = false;
private boolean verbose = false;
private boolean quiet = false;
public static void main(String[] args)
{
if (args.length < 1)
{
printUsage();
}
// Convert args to a list
List<String> argList = new ArrayList<String>(args.length);
List<String> argListFixed = Arrays.asList(args);
argList.addAll(argListFixed);
// Extract all the options
Map<String, String> optionValues = extractOptions(argList);
// Check for help request
if (optionValues.containsKey(OPTION_HELP))
{
printUsage();
System.exit(0);
}
// Check
if (argList.size() != 1)
{
printUsage();
System.exit(1);
}
// Get the directory to start in
File startDir = new File(argList.get(0));
if (!startDir.exists() || !startDir.isDirectory())
{
System.err.println("Convert: ");
System.err.println(" Unable to find directory: " + startDir);
System.err.flush();
printUsage();
System.exit(1);
}
Convert convert = new Convert(optionValues, startDir);
convert.convert();
}
/**
* Private constructor for use by the main method.
*/
private Convert(Map<String, String> optionValues, File startDir)
{
this.startDir = startDir;
svnStatus = optionValues.containsKey(OPTION_SVN_STATUS);
dryRun = optionValues.containsKey(OPTION_DRY_RUN);
String match = optionValues.get(OPTION_MATCH);
String ignore = optionValues.get(OPTION_IGNORE);
String encoding = optionValues.get(OPTION_ENCODING);
lineEnding = optionValues.get(OPTION_LINE_ENDING);
noRecurse = optionValues.containsKey(OPTION_NO_RECURSE);
noBackup = optionValues.containsKey(OPTION_NO_BACKUP);
verbose = optionValues.containsKey(OPTION_VERBOSE);
quiet = optionValues.containsKey(OPTION_QUIET);
// Check that the tab replacement count is correct
String replaceTabsStr = optionValues.get(OPTION_REPLACE_TABS);
if (replaceTabsStr != null)
{
try
{
replaceTabs = Integer.parseInt(replaceTabsStr);
}
catch (NumberFormatException e)
{
System.err.println("Convert: ");
System.err.println(" Unable to determine how many spaces to replace tabs with: " + replaceTabsStr);
System.err.flush();
printUsage();
System.exit(1);
}
}
// Check the match regex expressions
if (match == null)
{
match = ".*";
}
try
{
matchPattern = Pattern.compile(match);
}
catch (Throwable e)
{
System.err.println("Convert: ");
System.err.println(" Unable to parse regular expression: " + match);
System.err.flush();
printUsage();
System.exit(1);
}
// Check the match regex expressions
if (ignore != null)
{
try
{
ignorePattern = Pattern.compile(ignore);
}
catch (Throwable e)
{
System.err.println("Convert: ");
System.err.println(" Unable to parse regular expression: " + ignore);
System.err.flush();
printUsage();
System.exit(1);
}
}
// Check the encoding
if (encoding != null)
{
try
{
charset = Charset.forName(encoding);
}
catch (Throwable e)
{
System.err.println("Convert: ");
System.err.println(" Unknown encoding: " + encoding);
System.err.flush();
printUsage();
System.exit(1);
}
}
// Check line ending
if (lineEnding != null && !lineEnding.equals("WINDOWS") && !lineEnding.equals("UNIX"))
{
System.err.println("Convert: ");
System.err.println(" Line endings can be either WINDOWS or UNIX: " + lineEnding);
System.err.flush();
printUsage();
System.exit(1);
}
// Check quiet/verbose match
if (verbose && quiet)
{
System.err.println("Convert: ");
System.err.println(" Cannot output in verbose and quiet mode.");
System.err.flush();
printUsage();
System.exit(1);
}
}
private void convert()
{
try
{
if (!quiet)
{
System.out.print("Converting files matching " + matchPattern);
System.out.print(ignorePattern == null ? "" : " but not " + ignorePattern);
System.out.println(dryRun ? " [DRY RUN]" : "");
}
if (!svnStatus)
{
// Do a recursive pattern match
convertDir(startDir);
}
else
{
// Use SVN
convertSvn(startDir);
}
}
catch (Throwable e)
{
e.printStackTrace();
System.err.flush();
printUsage();
System.exit(1);
}
finally
{
System.out.flush();
}
}
private void convertSvn(File currentDir) throws Throwable
{
RuntimeExec exec = new RuntimeExec();
exec.setCommand(new String[] {"svn", "status", currentDir.toString()});
ExecutionResult result = exec.execute();
if (!result.getSuccess())
{
System.out.println("svn status command failed:" + exec);
}
// Get the output
String dump = result.getStdOut();
BufferedReader reader = null;
try
{
reader = new BufferedReader(new StringReader(dump));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
// Only lines that start with "A" or "M"
if (!line.startsWith("A") && !line.startsWith("M"))
{
continue;
}
String filename = line.substring(7).trim();
if (filename.length() < 1)
{
continue;
}
File file = new File(filename);
if (!file.exists())
{
continue;
}
// We found one
convertFile(file);
}
}
finally
{
if (reader != null)
{
try { reader.close(); } catch (Throwable e) {}
}
}
}
/**
* Recursive method to do the conversion work.
*/
private void convertDir(File currentDir) throws Throwable
{
// Get all children of the folder
File[] childFiles = currentDir.listFiles();
for (File childFile : childFiles)
{
if (childFile.isDirectory())
{
if (noRecurse)
{
// Don't enter the directory
continue;
}
// Recurse
convertDir(childFile);
}
else
{
convertFile(childFile);
}
}
}
private void convertFile(File file) throws Throwable
{
// We have a file, but does the pattern match
String filePath = file.getAbsolutePath();
if (matchPattern.matcher(filePath).find())
{
// It matches, but must we ignore it?
if (ignorePattern != null && ignorePattern.matcher(filePath).find())
{
// It is ignorable
return;
}
}
else
{
// It missed the primary positive match
return;
}
// Ignore folders
if (file.isDirectory())
{
return;
}
if (file.length() > (1024 * 1024)) // 1MB. TODO: Make an option
{
System.out.println(" (Too big)");
}
File backupFile = null;
try
{
// Read the source file into memory
byte[] fileBytes = readFileIntoMemory(file);
// Calculate the MD5 for the file
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(fileBytes);
byte[] fileMd5 = md5.digest();
// Guess the charset now
Charset fileCharset = guessCharset(fileBytes, charset);
byte[] convertedBytes = fileBytes;
byte[] sourceBytes = fileBytes;
byte[] convertedMd5 = fileMd5;
// Convert the tabs
if (replaceTabs != null)
{
sourceBytes = convertTabs(sourceBytes, fileCharset, replaceTabs);
}
// Convert the charset
if (charset != null)
{
// TODO
// sourceBytes = convert ...
}
// Convert the line endings
if (lineEnding != null)
{
convertedBytes = convertLineEndings(sourceBytes, fileCharset, lineEnding);
}
boolean changed = false;
if (convertedBytes == fileBytes)
{
// Nothing done
}
else
{
// Recalculate the converted MD5
md5 = MessageDigest.getInstance("MD5");
md5.update(convertedBytes);
convertedMd5 = md5.digest();
// Now compare
changed = !Arrays.equals(fileMd5, convertedMd5);
}
// Make a backup of the file if it changed
if (changed)
{
if (!noBackup && !dryRun)
{
String backupFilename = file.getAbsolutePath() + ".bak";
File backupFilePre = new File(backupFilename);
// Write the original file contents to the backup file
writeMemoryIntoFile(fileBytes, backupFilePre);
// That being successful, we can now reference it
backupFile = backupFilePre;
}
if (!quiet)
{
System.out.println(" " + file + " <Modified>");
}
// Only write to the file if this is not a dry run
if (!dryRun)
{
// Now write the converted buffer to the original file
writeMemoryIntoFile(convertedBytes, file);
}
}
else
{
if (verbose)
{
System.out.println(" " + file + " <No change>");
}
}
}
catch (Throwable e)
{
if (backupFile != null)
{
try
{
file.delete();
backupFile.renameTo(file);
}
catch (Throwable ee)
{
System.err.println("Failed to restore backup file: " + backupFile);
ee.printStackTrace();
}
}
throw e;
}
finally
{
if (!quiet || verbose)
{
System.out.flush();
}
}
}
/**
* Brute force guessing by doing charset conversions.<br/>
*/
private static Charset guessCharset(byte[] bytes, Charset charset) throws Exception
{
Charset guessedCharset = CHARACTER_ENCODING_FINDER.detectCharset(bytes);
if (guessedCharset == null)
{
return charset;
}
else
{
return guessedCharset;
}
}
private static byte[] convertTabs(byte[] bytes, Charset charset, int replaceTabs) throws Exception
{
// The tab character
char tab = '\t';
char space = ' ';
// The output
StringBuilder sb = new StringBuilder(bytes.length);
String charsetName = charset.name();
// Using the charset, convert to a string
String str = new String(bytes, charsetName);
char[] chars = str.toCharArray();
for (char c : chars)
{
if (c == tab)
{
// Replace the tab
for (int i = 0; i < replaceTabs; i++)
{
sb.append(space);
}
}
else
{
sb.append(c);
}
}
// Done
return sb.toString().getBytes(charsetName);
}
private static final String EOF_CHECK = "--EOF-CHECK--";
private static byte[] convertLineEndings(byte[] bytes, Charset charset, String lineEnding) throws Exception
{
String charsetName = charset.name();
// Using the charset, convert to a string
BufferedReader reader = null;
StringBuilder sb = new StringBuilder(bytes.length);
try
{
String str = new String(bytes, charsetName);
str = str + EOF_CHECK;
reader = new BufferedReader(new StringReader(str));
String line = reader.readLine();
while (line != null)
{
// Ignore the newline check
boolean addLine = true;
if (line.equals(EOF_CHECK))
{
break;
}
else if (line.endsWith(EOF_CHECK))
{
int index = line.indexOf(EOF_CHECK);
line = line.substring(0, index);
addLine = false;
}
// Write the line back out
sb.append(line);
if (!addLine)
{
// No newline
}
else if (lineEnding.equalsIgnoreCase("UNIX"))
{
sb.append("\n");
}
else
{
sb.append("\r\n");
}
line = reader.readLine();
}
}
finally
{
if (reader != null)
{
try { reader.close(); } catch (Throwable e) {}
}
}
// Done
return sb.toString().getBytes(charsetName);
}
private static byte[] readFileIntoMemory(File file) throws Exception
{
InputStream is = null;
OutputStream os = null;
try
{
is = new BufferedInputStream(new FileInputStream(file));
ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
os = new BufferedOutputStream(baos);
byte[] buffer = new byte[1024];
while (true)
{
int count = is.read(buffer);
if (count < 0)
{
break;
}
os.write(buffer, 0, count);
}
os.flush();
byte[] memory = baos.toByteArray();
return memory;
}
finally
{
if (is != null)
{
try { is.close(); } catch (Throwable e) {}
}
if (os != null)
{
try { os.close(); } catch (Throwable e) {}
}
}
}
private static void writeMemoryIntoFile(byte[] bytes, File file) throws Exception
{
InputStream is = null;
OutputStream os = null;
try
{
is = new ByteArrayInputStream(bytes);
os = new BufferedOutputStream(new FileOutputStream(file));
byte[] buffer = new byte[1024];
while (true)
{
int count = is.read(buffer);
if (count < 0)
{
break;
}
os.write(buffer, 0, count);
}
os.flush();
}
finally
{
if (is != null)
{
try { is.close(); } catch (Throwable e) {}
}
if (os != null)
{
try { os.close(); } catch (Throwable e) {}
}
}
}
/**
* Extract all the options from the list of arguments.
* @param args the program arguments. This list will be modified.
* @return Returns a map of arguments and their values. Where the arguments have
* no values, an empty string is returned.
*/
private static Map<String, String> extractOptions(List<String> args)
{
Map<String, String> optionValues = new HashMap<String, String>(13);
// Iterate until we find a non-option
Iterator<String> iterator = args.iterator();
while (iterator.hasNext())
{
String arg = iterator.next();
boolean foundOption = false;
for (String option : OPTIONS)
{
if (!arg.startsWith(option))
{
// It is a non-option
continue;
}
foundOption = true;
// We can remove the argument
iterator.remove();
// Check if the option needs a value
if (option.endsWith("="))
{
// Extract the option value
int index = arg.indexOf("=");
if (index == arg.length() - 1)
{
// There is nothing there, so we don't keep a value
}
else
{
String value = arg.substring(index + 1);
optionValues.put(option, value);
}
}
else
{
// Add the value to the map
String value = "";
optionValues.put(option, value);
}
}
if (!foundOption)
{
// It is not an option
break;
}
}
// Done
return optionValues;
}
public static void printUsage()
{
StringBuilder sb = new StringBuilder(1024);
sb.append("Usage: \n")
.append(" Convert [options] directory \n")
.append(" \n")
.append(" options: \n")
.append(" --help \n")
.append(" Print this help. \n")
.append(" --svn-status \n")
.append(" Execute a 'svn status' command against the directory and use the output for the file list. \n")
.append(" --match=?: \n")
.append(" A regular expression that all filenames must match. \n")
.append(" This argument can be escaped with double quotes, ie.g \"[a-zA-z0-9 ]\". \n")
.append(" The regular expression will be applied to the full path of the file. \n")
.append(" Name seperators will be '/' on Unix and ''\\'' on Windows systems. \n")
.append(" The default is \"--match=.*\", or match all files. \n")
.append(" --ignore=?: \n")
.append(" A regular expression that all filenames must not match. \n")
.append(" This argument can be escaped with double quotes, ie.g \"[a-zA-z0-9 ]\". \n")
.append(" The regular expression will be applied to the full path of the file. \n")
.append(" Name seperators will be '/' on Unix and ''\\'' on Windows systems. \n")
.append(" This option is not present by default. \n")
.append(" --encoding=? \n")
.append(" If not specified, the encoding of the files is left unchanged. \n")
.append(" Typical values would be UTF-8, UTF-16 or any java-recognized encoding string. \n")
.append(" --line-ending=? \n")
.append(" This can either be WINDOWS or UNIX. \n")
.append(" If not set, the line ending style is left unchanged. \n")
.append(" --replace-tabs=? \n")
.append(" Specify the number of spaces to insert in place of a tab. \n")
.append(" --no-recurse \n")
.append(" Do not recurse into subdirectories. \n")
.append(" --no-backup \n")
.append(" The default is to make a backup of all files prior to modification. \n")
.append(" With this option, no backups are made. \n")
.append(" --dry-run \n")
.append(" Do not modify or backup any files. \n")
.append(" No filesystem modifications are made. \n")
.append(" --verbose \n")
.append(" Dump all files checked to std.out. \n")
.append(" --quiet \n")
.append(" Don't dump anything to std.out. \n")
.append(" directory: \n")
.append(" The directory to start searching in. \n")
.append(" If the directory has spaces in it, then escape it with double quotes, e.g. \"C:\\Program Files\" \n")
.append(" \n")
.append("Details of the modifications being made are written to std.out. \n")
.append("Errors are written to std.err. \n")
.append("When used without any options, this program will behave like a FIND. \n");
System.out.println(sb);
System.out.flush();
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.net.URL;
/**
* Class containing debugging utility methods
*
* @author gavinc
*/
public class Debug
{
/**
* Returns the location of the file that will be loaded for the given class name
*
* @param className The class to load
* @return The location of the file that will be loaded
* @throws ClassNotFoundException
*/
public static String whichClass(String className) throws ClassNotFoundException
{
String path = className;
// prepare the resource path
if (path.startsWith("/") == false)
{
path = "/" + path;
}
path = path.replace('.', '/');
path = path + ".class";
// get the location
URL url = Debug.class.getResource(path);
if (url == null)
{
throw new ClassNotFoundException(className);
}
// format the result
String location = url.toExternalForm();
if (location.startsWith("jar"))
{
location = location.substring(10, location.lastIndexOf("!"));
}
else if (location.startsWith("file:"))
{
location = location.substring(6);
}
return location;
}
/**
* Returns the class loader that will load the given class name
*
* @param className The class to load
* @return The class loader the class will be loaded in
* @throws ClassNotFoundException
*/
public static String whichClassLoader(String className) throws ClassNotFoundException
{
String result = "Could not determine class loader for " + className;
Class clazz = Class.forName(className);
ClassLoader loader = clazz.getClassLoader();
if (loader != null)
{
result = clazz.getClassLoader().toString();
}
return result;
}
/**
* Returns the class loader hierarchy that will load the given class name
*
* @param className The class to load
* @return The hierarchy of class loaders used to load the class
* @throws ClassNotFoundException
*/
public static String whichClassLoaderHierarchy(String className) throws ClassNotFoundException
{
StringBuffer buffer = new StringBuffer();
Class clazz = Class.forName(className);
ClassLoader loader = clazz.getClassLoader();
if (loader != null)
{
buffer.append(loader.toString());
ClassLoader parent = loader.getParent();
while (parent != null)
{
buffer.append("\n-> ").append(parent.toString());
parent = parent.getParent();
}
}
else
{
buffer.append("Could not determine class loader for " + className);
}
return buffer.toString();
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.File;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility to delete a file or directory recursively.
* @author britt
*/
public class Deleter
{
private static final Log log = LogFactory.getLog(Deleter.class);
/**
* Delete by path.
* @param path
*/
public static void Delete(String path)
{
File toDelete = new File(path);
Delete(toDelete);
}
/**
* Delete by File.
* @param toDelete
*/
public static void Delete(File toDelete)
{
if (toDelete.isDirectory())
{
File[] listing = toDelete.listFiles();
for (File file : listing)
{
Delete(file);
}
}
toDelete.delete();
}
/**
* Recursively deletes the parents of the specified file stopping when <code>rootDir</code> is reached.
* The file itself must have been deleted before calling this method - since only empty
* directories can be deleted.
* <p>
* For example: <code>deleteEmptyParents(new File("/tmp/a/b/c/d.txt"), "/tmp/a")</code>
* <p>
* Will delete directories c and b assuming that they are both empty. It will leave /tmp/a even if it is
* empty as this is the <code>rootDir</code>
*
* @param file The path of the file whose parent directories should be deleted.
* @param rootDir Top level directory where deletion should stop. <strong>Must be the canonical path
* to ensure correct comparisons.</strong>
*/
public static void deleteEmptyParents(File file, String rootDir)
{
File parent = file.getParentFile();
boolean deleted = false;
do
{
try
{
if (parent.isDirectory() && !parent.getCanonicalPath().equals(rootDir))
{
// Only an empty directory will successfully be deleted.
deleted = parent.delete();
}
}
catch (IOException error)
{
log.error("Unable to construct canonical path for " + parent.getAbsolutePath());
break;
}
parent = parent.getParentFile();
}
while(deleted);
}
/**
* Same behaviour as for {@link Deleter#deleteEmptyParents(File, String)} but with the
* <code>rootDir</code> parameter specified as a {@link java.io.File} object.
*
* @see Deleter#deleteEmptyParents(File, String)
* @param file
* @param rootDir
*/
public static void deleteEmptyParents(File file, File rootDir)
{
try
{
deleteEmptyParents(file, rootDir.getCanonicalPath());
}
catch (IOException e)
{
String msg = "Unable to convert rootDir to canonical form [rootDir=" + rootDir + "]";
throw new RuntimeException(msg, e);
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This is an instance of {@link java.util.concurrent.ThreadPoolExecutor} which
* behaves how one would expect it to, even when faced with an unlimited
* queue. Unlike the default {@link java.util.concurrent.ThreadPoolExecutor}, it
* will add new Threads up to {@link #setMaximumPoolSize(int) maximumPoolSize}
* when there is lots of pending work, rather than only when the queue is full
* (which it often never will be, especially for unlimited queues)
*
* @author Nick Burch
*/
public class DynamicallySizedThreadPoolExecutor extends ThreadPoolExecutor
{
private static Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutor.class);
private final ReentrantLock lock = new ReentrantLock();
private int realCorePoolSize;
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
this.realCorePoolSize = corePoolSize;
}
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
this.realCorePoolSize = corePoolSize;
}
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
this.realCorePoolSize = corePoolSize;
}
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.realCorePoolSize = corePoolSize;
}
@Override
public void setCorePoolSize(int corePoolSize)
{
this.realCorePoolSize = corePoolSize;
super.setCorePoolSize(corePoolSize);
}
@Override
public void execute(Runnable command)
{
// Do we want to add another thread?
int threadCount = getPoolSize();
if(logger.isDebugEnabled())
{
logger.debug("Current pool size is " + threadCount + ", real core=" + realCorePoolSize +
", current core=" + getCorePoolSize() + ", max=" + getMaximumPoolSize());
}
if(threadCount < getMaximumPoolSize())
{
// We're not yet at the full thread count
// Does the queue size warrant adding one?
// (If there are more than the maximum pool size of jobs pending,
// it's time to add another thread)
int queueSize = getQueue().size() + 1;// New job not yet added
if(queueSize >= getMaximumPoolSize())
{
lock.lock();
int currentCoreSize = getCorePoolSize();
if(currentCoreSize < getMaximumPoolSize())
{
super.setCorePoolSize(currentCoreSize+1);
if(logger.isInfoEnabled())
{
logger.info("Increased pool size to " + getCorePoolSize() + " from " +
currentCoreSize + " due to queue size of " + queueSize);
}
}
lock.unlock();
}
}
// Now run the actual work
super.execute(command);
}
@Override
protected void afterExecute(Runnable r, Throwable t)
{
// If the queue is looking empty, allow the pool to
// get rid of idle threads when it wants to
int threadCount = getPoolSize();
if(threadCount == getMaximumPoolSize() && threadCount > realCorePoolSize)
{
int queueSize = getQueue().size();
int currentCoreSize = getCorePoolSize();
if(queueSize < 2 && currentCoreSize > realCorePoolSize)
{
// Almost out of work, allow the pool to reduce threads when
// required. Double checks the sizing inside a lock to avoid
// race conditions taking us below the real core size.
lock.lock();
currentCoreSize = getCorePoolSize();
if(currentCoreSize > realCorePoolSize)
{
super.setCorePoolSize(currentCoreSize-1);
if(logger.isInfoEnabled())
{
logger.info("Decreased pool size to " + getCorePoolSize() + " from " +
currentCoreSize + " (real core size is " + realCorePoolSize +
") due to queue size of " + queueSize);
}
}
lock.unlock();
}
}
}
}

View File

@@ -0,0 +1,261 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.alfresco.api.AlfrescoPublicApi;
/**
* Utility class providing helper methods for various types of <code>equals</code> functionality
*
* @author Derek Hulley
*/
@AlfrescoPublicApi
public class EqualsHelper
{
/**
* Performs an equality check <code>left.equals(right)</code> after checking for null values
*
* @param left the Object appearing in the left side of an <code>equals</code> statement
* @param right the Object appearing in the right side of an <code>equals</code> statement
* @return Return true or false even if one or both of the objects are null
*/
public static boolean nullSafeEquals(Object left, Object right)
{
return (left == right) || (left != null && right != null && left.equals(right));
}
/**
* Performs an case-sensitive or case-insensitive equality check after checking for null values
* @param ignoreCase <tt>true</tt> to ignore case
*/
public static boolean nullSafeEquals(String left, String right, boolean ignoreCase)
{
if (ignoreCase)
{
return (left == right) || (left != null && right != null && left.equalsIgnoreCase(right));
}
else
{
return (left == right) || (left != null && right != null && left.equals(right));
}
}
private static final int BUFFER_SIZE = 1024;
/**
* Performs a byte-level comparison between two streams.
*
* @param left the left stream. This is closed at the end of the operation.
* @param right an right stream. This is closed at the end of the operation.
* @return Returns <tt>true</tt> if the streams are identical to the last byte
*/
public static boolean binaryStreamEquals(InputStream left, InputStream right) throws IOException
{
try
{
if (left == right)
{
// The same stream! This is pretty pointless, but they are equal, nevertheless.
return true;
}
byte[] leftBuffer = new byte[BUFFER_SIZE];
byte[] rightBuffer = new byte[BUFFER_SIZE];
while (true)
{
int leftReadCount = left.read(leftBuffer);
int rightReadCount = right.read(rightBuffer);
if (leftReadCount != rightReadCount)
{
// One stream ended before the other
return false;
}
else if (leftReadCount == -1)
{
// Both streams ended without any differences found
return true;
}
for (int i = 0; i < leftReadCount; i++)
{
if (leftBuffer[i] != rightBuffer[i])
{
// We found a byte difference
return false;
}
}
}
// The only exits with 'return' statements, so there is no need for any code here
}
finally
{
try { left.close(); } catch (Throwable e) {}
try { right.close(); } catch (Throwable e) {}
}
}
/**
* Compare two maps and generate a difference report between the actual and expected values.
* This method is particularly useful during unit tests as the result (if not <tt>null</tt>)
* can be appended to a failure message.
*
* @param actual the map in hand
* @param expected the map expected
* @return Returns a difference report or <tt>null</tt> if there were no
* differences. The message starts with a new line and is neatly
* formatted.
*/
public static String getMapDifferenceReport(Map<?, ?> actual, Map<?, ?> expected)
{
Map<?, ?> copyResult = new HashMap<Object, Object>(actual);
boolean failure = false;
StringBuilder sb = new StringBuilder(1024);
sb.append("\nValues that don't match the expected values: ");
for (Map.Entry<?, ?> entry : expected.entrySet())
{
Object key = entry.getKey();
Object expectedValue = entry.getValue();
Object resultValue = actual.get(key);
if (!EqualsHelper.nullSafeEquals(resultValue, expectedValue))
{
sb.append("\n")
.append(" Key: ").append(key).append("\n")
.append(" Result: ").append(resultValue).append("\n")
.append(" Expected: ").append(expectedValue);
failure = true;
}
copyResult.remove(key);
}
sb.append("\nValues that are present but should not be: ");
for (Map.Entry<?, ?> entry : copyResult.entrySet())
{
Object key = entry.getKey();
Object resultValue = entry.getValue();
sb.append("\n")
.append(" Key: ").append(key).append("\n")
.append(" Result: ").append(resultValue);
failure = true;
}
if (failure)
{
return sb.toString();
}
else
{
return null;
}
}
/**
* Enumeration for results returned by {@link EqualsHelper#getMapComparison(Map, Map) map comparisons}.
*
* @author Derek Hulley
* @since 3.3
*/
public static enum MapValueComparison
{
/** The key was only present in the left map */
LEFT_ONLY,
/** The key was only present in the right map */
RIGHT_ONLY,
/** The key was present in both maps and the values were equal */
EQUAL,
/** The key was present in both maps but not equal */
NOT_EQUAL
}
/**
* Compare two maps.
* <p/>
* The return codes that accompany the keys are:
* <ul>
* <li>{@link MapValueComparison#LEFT_ONLY}</li>
* <li>{@link MapValueComparison#RIGHT_ONLY}</li>
* <li>{@link MapValueComparison#EQUAL}</li>
* <li>{@link MapValueComparison#NOT_EQUAL}</li>
* </ul>
*
* @param <K> the map key type
* @param <V> the map value type
* @param left the left side of the comparison
* @param right the right side of the comparison
* @return Returns a map whose keys are a union of the two maps' keys, along with
* the value comparison result
*/
public static <K, V> Map<K, MapValueComparison> getMapComparison(Map<K, V> left, Map<K, V> right)
{
Set<K> keys = new HashSet<K>(left.size() + right.size());
keys.addAll(left.keySet());
keys.addAll(right.keySet());
Map<K, MapValueComparison> diff = new HashMap<K, MapValueComparison>(left.size() + right.size());
// Iterate over the keys and do the comparisons
for (K key : keys)
{
boolean leftHasKey = left.containsKey(key);
boolean rightHasKey = right.containsKey(key);
V leftValue = left.get(key);
V rightValue = right.get(key);
if (leftHasKey)
{
if (!rightHasKey)
{
diff.put(key, MapValueComparison.LEFT_ONLY);
}
else if (EqualsHelper.nullSafeEquals(leftValue, rightValue))
{
diff.put(key, MapValueComparison.EQUAL);
}
else
{
diff.put(key, MapValueComparison.NOT_EQUAL);
}
}
else if (rightHasKey)
{
if (!leftHasKey)
{
diff.put(key, MapValueComparison.RIGHT_ONLY);
}
else if (EqualsHelper.nullSafeEquals(leftValue, rightValue))
{
diff.put(key, MapValueComparison.EQUAL);
}
else
{
diff.put(key, MapValueComparison.NOT_EQUAL);
}
}
else
{
// How is it here?
}
}
return diff;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.Serializable;
/**
* Simple cache of a single Object value.
* <p>
* The object placed in the cache will automatically be discarded after a timeout value.
*
* @author Kevin Roast
*/
public class ExpiringValueCache<T> implements Serializable
{
private static final long serialVersionUID = 1036233352030777619L;
// default is to discard cached object after 1 minute
private final static long TIMEOUT_DEFAULT = 1000L*60L;
private long timeout = TIMEOUT_DEFAULT;
private long snapshot = 0;
private T value;
/**
* Default constructor.
*
* Uses the default timeout of 1 minute.
*/
public ExpiringValueCache()
{
}
/**
* Constructor
*
* @param timeout Timeout in milliseconds before cached value is discarded
*/
public ExpiringValueCache(long timeout)
{
this.timeout = timeout;
}
/**
* Put a value into the cache. The item will be return from the associated get() method
* until the timeout expires then null will be returned.
*
* @param value The object to store in the cache
*/
public void put(T value)
{
this.value = value;
this.snapshot = System.currentTimeMillis();
}
/**
* Get the cached object. The set item will be returned until it expires, then null will be returned.
*
* @return cached object or null if not set or expired.
*/
public T get()
{
if (snapshot + timeout < System.currentTimeMillis())
{
this.value = null;
}
return this.value;
}
/**
* Clear the cache value
*/
public void clear()
{
this.value = null;
}
}

Some files were not shown because too many files have changed in this diff Show More