Moved repository master into its own directory
32
repository/.externalToolBuilders/JibX.launch
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
|
||||
<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
|
||||
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="true"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/Repository/build.xml"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
|
||||
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry id="org.eclipse.ant.ui.classpathentry.antHome"> <memento default="true"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry id="org.eclipse.ant.ui.classpathentry.extraClasspathEntries"> <memento/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry internalArchive="/3rd Party/lib/jibx-run-1.2.5.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry internalArchive="/3rd Party/lib/jibx-bind-1.2.5.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry internalArchive="/3rd Party/lib/bcel.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8"?> <runtimeClasspathEntry internalArchive="/3rd Party/lib/xpp3-1.1.3_8.jar" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.ant.internal.ui.antsupport.InternalAntRunner"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="Repository"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:Repository}"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:<?xml version="1.0" encoding="UTF-8"?> <launchConfigurationWorkingSet editPageId="org.eclipse.ui.resourceWorkingSetPage" factoryID="org.eclipse.ui.internal.WorkingSetFactory" id="1264001019976_1" label="workingSet" name="workingSet"> <item factoryID="org.eclipse.ui.internal.model.ResourceFactory" path="/Repository/source/java/org/alfresco/repo/dictionary" type="2"/> </launchConfigurationWorkingSet>}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Repository/build.xml}"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,clean"/>
|
||||
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
|
||||
<stringAttribute key="process_factory_id" value="org.eclipse.ant.ui.remoteAntProcessFactory"/>
|
||||
</launchConfiguration>
|
4
repository/.gitbugtraq
Normal file
@@ -0,0 +1,4 @@
|
||||
# For SmartGit
|
||||
[bugtraq "jira"]
|
||||
url = https://issues.alfresco.com/jira/browse/%BUGID%
|
||||
logRegex = ([A-Z]+-\\d+)
|
40
repository/.gitignore
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
*.class
|
||||
|
||||
# Eclipse
|
||||
.classpath
|
||||
.settings
|
||||
.project
|
||||
|
||||
# Intellij
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
#VSCode
|
||||
/.vscode
|
||||
|
||||
# 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*
|
||||
alf_data
|
||||
/src/main/resources/alfresco-global.properties
|
||||
/src/main/resources/alfresco/extension/custom-log4j.properties
|
15
repository/.travis.settings.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<settings>
|
||||
<!-- required to push artifacts to repositories -->
|
||||
<servers>
|
||||
<server>
|
||||
<id>alfresco-public</id>
|
||||
<username>${env.MAVEN_USERNAME}</username>
|
||||
<password>${env.MAVEN_PASSWORD}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>quay.io</id>
|
||||
<username>${env.QUAY_USERNAME}</username>
|
||||
<password>${env.QUAY_PASSWORD}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
122
repository/.travis.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
dist: xenial
|
||||
sudo: required
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk11
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
# the cache can grow constantly
|
||||
before_cache:
|
||||
- rm -rf $HOME/.m2/repository/org/alfresco/alfresco-repository
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /support\/.*/
|
||||
|
||||
stages:
|
||||
- test
|
||||
- release
|
||||
|
||||
install: travis_retry mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
name: "AllUnitTestsSuite"
|
||||
script: mvn test -B -Dtest=AllUnitTestsSuite
|
||||
- name: "WhiteSource scan"
|
||||
# only on support 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: "AppContext01TestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
script: travis_wait 20 mvn test -B -Dtest=AppContext01TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
- name: "AppContext02TestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
script: travis_wait 20 mvn test -B -Dtest=AppContext02TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
- name: "AppContext03TestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
- docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.2
|
||||
script: travis_wait 20 mvn test -B -Dtest=AppContext03TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco -Dalfresco-pdf-renderer.url=http://localhost:8090/ -Djodconverter.url=http://localhost:8090/ -Dimg.url=http://localhost:8090/ -Dtika.url=http://localhost:8090/ -Dtransform.misc.url=http://localhost:8090/
|
||||
- name: "AppContext04TestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
- docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.2
|
||||
script: travis_wait 20 mvn test -B -Dtest=AppContext04TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
- name: "AppContext05TestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
- "mkdir -p $HOME/tmp"
|
||||
- "cp src/test/resources/realms/alfresco-realm.json $HOME/tmp"
|
||||
- docker login quay.io -u ${QUAY_USERNAME} -p ${QUAY_PASSWORD}
|
||||
- "export HOST_IP=$(ip address show | grep -E \"([0-9]{1,3}\\.){3}[0-9]{1,3}\" | grep -v 127.0.0.1 | awk '{ print $2 }' | head -n 1 )"
|
||||
- "export HOST_IP=$(echo ${HOST_IP%/*})"
|
||||
- docker run -d -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_VENDOR=h2 -p 8999:8080 -e KEYCLOAK_IMPORT=/tmp/alfresco-realm.json -v $HOME/tmp/alfresco-realm.json:/tmp/alfresco-realm.json quay.io/alfresco/alfresco-identity-service:1.2
|
||||
script: travis_wait 20 mvn test -B -Dtest=AppContext05TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "-Didentity-service.auth-server-url=http://${HOST_IP}:8999/auth"
|
||||
- name: "AppContext06TestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
- docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.2
|
||||
script: travis_wait 20 mvn test -B -Dtest=AppContext06TestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco -Dalfresco-pdf-renderer.url=http://localhost:8090/ -Djodconverter.url=http://localhost:8090/ -Dimg.url=http://localhost:8090/ -Dtika.url=http://localhost:8090/ -Dtransform.misc.url=http://localhost:8090/
|
||||
- name: "AppContextExtraTestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
script: travis_wait 20 mvn test -B -Dtest=AppContextExtraTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
- name: "MiscContextTestSuite"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
- docker run -d -p 8090:8090 -e JAVA_OPTS=" -Xms256m -Xmx256m" alfresco/alfresco-transform-core-aio:2.3.2
|
||||
script: travis_wait 20 mvn test -B -Dtest=MiscContextTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco -Dalfresco-pdf-renderer.url=http://localhost:8090/ -Djodconverter.url=http://localhost:8090/ -Dimg.url=http://localhost:8090/ -Dtika.url=http://localhost:8090/ -Dtransform.misc.url=http://localhost:8090/
|
||||
- name: "MySQL tests"
|
||||
before_install:
|
||||
- docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=alfresco -e MYSQL_USER=alfresco -e MYSQL_DATABASE=alfresco -e MYSQL_PASSWORD=alfresco mysql:5.7.23 --transaction-isolation='READ-COMMITTED'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
script: travis_wait 20 mvn test -B -Dtest=AllDBTestsTestSuite -Ddb.driver=com.mysql.jdbc.Driver -Ddb.name=alfresco -Ddb.url=jdbc:mysql://localhost:3307/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
- name: "PostgreSQL 10 tests"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:10.9 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
script: travis_wait 20 mvn test -B -Dtest=AllDBTestsTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
- name: "PostgreSQL 11 tests"
|
||||
before_install:
|
||||
- docker run -d -p 5433:5432 -e POSTGRES_PASSWORD=alfresco -e POSTGRES_USER=alfresco -e POSTGRES_DB=alfresco postgres:11.7 postgres -c 'max_connections=300'
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
script: travis_wait 20 mvn test -B -Dtest=AllDBTestsTestSuite -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql://localhost:5433/alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
- name: "MariaDB tests"
|
||||
before_install:
|
||||
- docker run -d -p 3307:3306 --name mariadb -e MYSQL_ROOT_PASSWORD=alfresco -e MYSQL_USER=alfresco -e MYSQL_DATABASE=alfresco -e MYSQL_PASSWORD=alfresco mariadb:10.2.18 --transaction-isolation=READ-COMMITTED --max-connections=300 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
- docker run -d -p 61616:61616 -p 5672:5672 alfresco/alfresco-activemq:5.15.8
|
||||
script: travis_wait 20 mvn test -B -Dtest=AllDBTestsTestSuite -Ddb.name=alfresco -Ddb.url=jdbc:mariadb://localhost:3307/alfresco?useUnicode=yes\&characterEncoding=UTF-8 -Ddb.username=alfresco -Ddb.password=alfresco -Ddb.driver=org.mariadb.jdbc.Driver
|
||||
- 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
repository/.whitesource
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"generalSettings": {
|
||||
"shouldScanRepo": true
|
||||
},
|
||||
"checkRunSettings": {
|
||||
"vulnerableCheckRunConclusionLevel": "failure"
|
||||
}
|
||||
}
|
228
repository/.wss-unified-agent.config
Normal 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-repository
|
||||
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
repository/CONTRIBUTING.md
Normal 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
repository/LICENSE
Normal 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.
|
41
repository/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
### Alfresco Repository
|
||||
[](https://travis-ci.com/Alfresco/alfresco-repository)
|
||||
|
||||
Repository 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:
|
||||
* DAOs and SQL scripts
|
||||
* Various Service implementations
|
||||
* Utility classes
|
||||
|
||||
### Building and testing
|
||||
The project can be built by running Maven command:
|
||||
~~~
|
||||
mvn clean install
|
||||
~~~
|
||||
The tests are combined in test classes split by test type or Spring application context used in the test, see classes in _src/test/java/org/alfresco_. All of these classes as well as individual tests can be run by specifying the test class name and a set of DB connection properties, for example:
|
||||
~~~
|
||||
mvn clean test -Dtest=SomeRepoTest -Ddb.driver=org.postgresql.Driver -Ddb.name=alfresco -Ddb.url=jdbc:postgresql:alfresco -Ddb.username=alfresco -Ddb.password=alfresco
|
||||
~~~
|
||||
|
||||
### 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-repository</artifactId>
|
||||
<version>version</version>
|
||||
</dependency>
|
||||
~~~
|
||||
and Alfresco Maven 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.
|
||||
|
||||
### Contributing guide
|
||||
Please use [this guide](CONTRIBUTING.md) to make a contribution to the project.
|
66
repository/docs/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Community Technical Documentation Index
|
||||
|
||||
## Repository
|
||||
|
||||
### Meta-data Services
|
||||
* [Node Storage and Retrieval](./meta-data-services/node-storage-and-retrieval)
|
||||
* Dictionary
|
||||
* Files and Folders
|
||||
* Smart Folders
|
||||
* [Versions](./meta-data-services/versions)
|
||||
* Permissions
|
||||
* Tagging
|
||||
* Multilingual Content
|
||||
|
||||
### Content Store
|
||||
* Content Storage and Retrieval
|
||||
|
||||
### Rules Engine
|
||||
* Rules
|
||||
* Actions
|
||||
|
||||
### Workflow
|
||||
* Embedded Activiti
|
||||
|
||||
### Transformation
|
||||
* Renditions
|
||||
|
||||
### Content Analysis
|
||||
* Meta-data Extraction
|
||||
|
||||
### File Protocols
|
||||
* FTP Protocol
|
||||
* WebDAV Protocol
|
||||
|
||||
### Application Protocols
|
||||
* IMAP Protocol
|
||||
* Office Protocols
|
||||
|
||||
### Scripting
|
||||
* JavaScript API
|
||||
* Freemarker API
|
||||
|
||||
### Sync and Transfer
|
||||
* Content Replication
|
||||
* Content Transfer
|
||||
|
||||
### Event Log
|
||||
* Audit
|
||||
* Log Messages
|
||||
|
||||
### Identity Provider
|
||||
* [Authentication](./identity-provider/authentication)
|
||||
* LDAP Sync
|
||||
|
||||
### Deployment
|
||||
* Installer
|
||||
* MMT
|
||||
* Patch
|
||||
|
||||
### Messaging
|
||||
* Messages and Topics
|
||||
|
||||
### Infrastructure
|
||||
* Module Framework
|
||||
* Policies and Behaviours
|
||||
* Multi-tenancy
|
BIN
repository/docs/event-log/audit/resource/data/audit-data.png
Normal file
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,41 @@
|
||||
@startuml
|
||||
|
||||
title Audit and Attributes - High Level Entities
|
||||
|
||||
skinparam linetype ortho
|
||||
|
||||
rectangle AuditDAO #lightgrey {
|
||||
rectangle AuditApplication #orange
|
||||
rectangle AuditModel #white
|
||||
rectangle ModelContent #white
|
||||
rectangle AuditEntry #white
|
||||
}
|
||||
|
||||
rectangle PropertyValueDAO #lightgrey {
|
||||
rectangle Attribute #orange
|
||||
rectangle ComplexProperty #white
|
||||
rectangle SimpleProperty #white
|
||||
rectangle Value #white
|
||||
rectangle Caches #white {
|
||||
rectangle PropertyUniqueContextSharedCache #white
|
||||
rectangle PropertyClassSharedCache #white
|
||||
rectangle PropertyValueSharedCache #white
|
||||
}
|
||||
}
|
||||
|
||||
AuditApplication --* AuditModel
|
||||
AuditApplication --* AuditEntry
|
||||
AuditModel -- ModelContent
|
||||
PropertyClassSharedCache ->SimpleProperty
|
||||
PropertyValueSharedCache ->SimpleProperty
|
||||
PropertyUniqueContextSharedCache ->Attribute
|
||||
SimpleProperty .. Value :value
|
||||
SimpleProperty -- Attribute: keys (1 to 3)
|
||||
ComplexProperty -- SimpleProperty
|
||||
ComplexProperty -- AuditEntry :audit values
|
||||
ComplexProperty -- AuditApplication :disabled paths
|
||||
Attribute -- ComplexProperty
|
||||
|
||||
center footer Copyright 2016 Alfresco Software Inc
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 206 KiB |
After Width: | Height: | Size: 193 KiB |
@@ -0,0 +1,86 @@
|
||||
@startuml
|
||||
|
||||
' Generated using https://github.com/juanmf/Java2PlantUML
|
||||
|
||||
left to right direction
|
||||
' Participants
|
||||
|
||||
interface org.alfresco.jlan.ftp.FTPAuthenticator {
|
||||
--
|
||||
+ authenticateUser(c ClientInfo, c FTPSrvSession) : boolean
|
||||
+ closeAuthenticator() : void
|
||||
+ initialize(c ServerConfiguration, i ConfigElement) : void
|
||||
|
||||
}
|
||||
interface org.springframework.beans.factory.DisposableBean {
|
||||
--
|
||||
+ destroy() : void
|
||||
|
||||
}
|
||||
interface org.alfresco.filesys.auth.ftp.package-info {
|
||||
--
|
||||
|
||||
}
|
||||
interface org.alfresco.repo.management.subsystems.ActivateableBean {
|
||||
--
|
||||
+ isActive() : boolean
|
||||
|
||||
}
|
||||
class org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase {
|
||||
# logger : i Log
|
||||
# serverConfiguration : i ServerConfigurationAccessor
|
||||
- active : boolean
|
||||
- authenticationComponent : i AuthenticationComponent
|
||||
- authenticationService : i AuthenticationService
|
||||
- authorityService : i AuthorityService
|
||||
- transactionService : i TransactionService
|
||||
--
|
||||
+ FTPAuthenticatorBase()
|
||||
# checkForAdminUserName(c ClientInfo) : void
|
||||
# createTransaction() : UserTransaction
|
||||
# getAuthenticationComponent() : AuthenticationComponent
|
||||
# getAuthenticationService() : AuthenticationService
|
||||
# getAuthorityService() : AuthorityService
|
||||
# getTransactionService() : TransactionService
|
||||
+ authenticateUser(c ClientInfo, c FTPSrvSession) : boolean
|
||||
+ closeAuthenticator() : void
|
||||
+ destroy() : void
|
||||
+ initialize() : void
|
||||
+ initialize(c ServerConfiguration, i ConfigElement) : void
|
||||
+ isActive() : boolean
|
||||
+ setActive(boolean) : void
|
||||
+ setAuthenticationComponent(i AuthenticationComponent) : void
|
||||
+ setAuthenticationService(i AuthenticationService) : void
|
||||
+ setAuthorityService(i AuthorityService) : void
|
||||
+ setConfig(i ServerConfigurationAccessor) : void
|
||||
+ setTransactionService(i TransactionService) : void
|
||||
|
||||
}
|
||||
class org.alfresco.filesys.auth.ftp.AlfrescoFtpAuthenticator {
|
||||
# m_encryptor : c PasswordEncryptor
|
||||
# m_md4Encoder : i MD4PasswordEncoder
|
||||
--
|
||||
+ AlfrescoFtpAuthenticator()
|
||||
# doGuestLogon(c AlfrescoClientInfo, c SrvSession) : void
|
||||
+ authenticateUser(c ClientInfo, c FTPSrvSession) : boolean
|
||||
|
||||
}
|
||||
|
||||
' Relations
|
||||
|
||||
org.alfresco.filesys.auth.ftp.AlfrescoFtpAuthenticator "1" o-left- "1" org.alfresco.repo.security.authentication.MD4PasswordEncoder : m_md4Encoder: i MD4PasswordEncoder
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase "1" o-left- "1" org.alfresco.service.transaction.TransactionService : transactionService: i TransactionService
|
||||
org.alfresco.filesys.auth.ftp.AlfrescoFtpAuthenticator "1" o-left- "1" org.alfresco.jlan.server.auth.PasswordEncryptor : m_encryptor: c PasswordEncryptor
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase "1" o-left- "1" org.apache.commons.logging.Log : logger: i Log
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase "1" o-left- "1" org.alfresco.jlan.server.config.ServerConfigurationAccessor : serverConfiguration: i ServerConfigurationAccessor
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase "1" o-left- "1" org.alfresco.repo.security.authentication.AuthenticationComponent : authenticationComponent: i AuthenticationComponent
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase "1" o-left- "1" org.alfresco.service.cmr.security.AuthorityService : authorityService: i AuthorityService
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase "1" o-left- "1" org.alfresco.service.cmr.security.AuthenticationService : authenticationService: i AuthenticationService
|
||||
org.alfresco.filesys.auth.ftp.AlfrescoFtpAuthenticator -up|> org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase ..up|> org.alfresco.jlan.ftp.FTPAuthenticator
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase ..up|> org.alfresco.repo.management.subsystems.ActivateableBean
|
||||
org.alfresco.filesys.auth.ftp.FTPAuthenticatorBase ..up|> org.springframework.beans.factory.DisposableBean
|
||||
|
||||
' Notes
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 808 KiB |
165
repository/docs/identity-provider/authentication/README.md
Normal file
@@ -0,0 +1,165 @@
|
||||
## Authentication
|
||||
|
||||

|
||||
|
||||
### Purpose
|
||||
|
||||
The purpose of this sub-component is to authenticate users.
|
||||
***
|
||||
|
||||
### Overview
|
||||
|
||||
In order to use any service in Alfresco, a user must be authenticated.
|
||||
|
||||
Alfresco provides a default Authentication implementation that uses userid's and passwored
|
||||
managed by Alfresco. But, importantly, Alfresco also allows the customer
|
||||
to integrate with a number of external Authentication providers including
|
||||
* Active Directory
|
||||
* Kerberos
|
||||
* LDAP
|
||||
|
||||
***
|
||||
|
||||
### Artifacts and Guidance
|
||||
|
||||
* Source Code Links:
|
||||
* https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root/enterpriseprojects/repository/source/java/
|
||||
* https://github.com/Alfresco/alfresco-data-model/tree/master/src/main/java/org/alfresco/repo/security/authentication
|
||||
* License: LGPL
|
||||
* Issue Tracker Link: https://issues.alfresco.com/jira/issues/?jql=project%3DREPO
|
||||
* Documentation Link: http://docs.alfresco.com/5.2/concepts/auth-intro.html
|
||||
* Contribution Model: Alfresco Open Source
|
||||
***
|
||||
|
||||
|
||||
### Prerequisite Knowledge
|
||||
|
||||
* [Acegi](http://springinpractice.com/2008/02/26/acegi-overview)
|
||||
* [CAS](https://en.wikipedia.org/wiki/Central_Authentication_Service)
|
||||
* [JAAS](http://docs.oracle.com/javase/8/docs/technotes/guides/security/jaas/JAASRefGuide.html)
|
||||
* [Kerberos](https://msdn.microsoft.com/en-us/library/bb742516.aspx)
|
||||
* [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)
|
||||
***
|
||||
|
||||
### Design
|
||||
|
||||
#### Default Authentication
|
||||
|
||||
In order to use any service in Alfresco, a user or client must be authenticated.
|
||||
|
||||
There is a default implementation provided that authenticates users based on a userid and password, where the userid's and passwords
|
||||
are stored in the Alfresco repository.
|
||||
|
||||
#### Chaining
|
||||
|
||||
Most production systems that use Alfresco will rely upon more secure approaches, so Alfresco also allows the
|
||||
customer to integrate a choice of existing authentication providers, including *Active Directory*, *Kerberos* and *LDAP*.
|
||||
|
||||
The implementation of each such Authorization provider is delivered as a separate Alfresco Subsystem.
|
||||
The Subsystems are chained together as an ordered list of providers each of which, in turn, will be given
|
||||
a chance to authenticate the user, until the user is authenticated or there are no providers, in
|
||||
which case the authentication of the user fails.
|
||||
|
||||
|
||||
#### Component Model
|
||||
|
||||
#### Data Model
|
||||

|
||||
|
||||
#### Data Dictionary
|
||||
|
||||
#### Flows
|
||||
|
||||
##### Login Flow
|
||||
|
||||
##### Default Authentication Login Flow
|
||||
This flow starts when the Login Post request
|
||||
depicted in [Client Login](../../../share/share-app/resource/sequence/client-login-sequence.png) reaches the repository tier.
|
||||

|
||||
|
||||
##### Default Authentication Logoff Flow
|
||||

|
||||
|
||||
##### Kerberos Authentication Login Flow
|
||||

|
||||
|
||||
#### Class Diagram
|
||||

|
||||
|
||||
|
||||
### APIs and Interfaces
|
||||
|
||||
#### Java
|
||||
|
||||
The sub-component provides a definition and implementation of the following
|
||||
Java interfaces.
|
||||
|
||||
* **Authentication Service**
|
||||
**Note**: This service is part of the Public API
|
||||
* authenticate using a user name and password
|
||||
* authenticate using a ticket
|
||||
* create, update and delete authentication information
|
||||
* clear the current authentication
|
||||
* invalidate a ticket
|
||||
* get the username for who is currently authenticated
|
||||
* get a ticket for subsequent re-authentication
|
||||
* determine if the current user is 'the system user
|
||||
* **MutableAuthenticationService*
|
||||
|
||||
In addition, there are a number of related interfaces that provide
|
||||
a way to manage users and groups and permissions
|
||||

|
||||
*This information should probably move to the Identity sub-component after the content already there (future design thoughts)
|
||||
is relocated*
|
||||
|
||||
* **Authority Service**
|
||||
**Note**: This service is part of the Public API
|
||||
* create authority identifiers
|
||||
* query for authority identifiers
|
||||
* delete authority identifiers
|
||||
* organize authority identifiers into hierarchies
|
||||
* query against authority identifiers hierarchies
|
||||
* find all the authorties that apply to the current authenticated user
|
||||
* determine if the current authenticated user has admin rights
|
||||
* **Ownable Service**
|
||||
**Note**: This service is **not** part of the Public API
|
||||
* determine the owner of a node;
|
||||
* set the owner of a node;
|
||||
* determine if a node has an owner
|
||||
* allow the current user to take ownership of a node
|
||||
* **Person Service**
|
||||
**Note**: This service is part of the Public API
|
||||
* obtain a reference to the Person node for a given user name
|
||||
* determine if a person entry exists for a user
|
||||
* create missing people entries, with default settings, on demand
|
||||
* supply a list of mutable properties for each person
|
||||
* create, delete and update personal information
|
||||
***
|
||||
#### REST
|
||||
|
||||
The sub-component provides the following REST API
|
||||
* https://api-explorer.alfresco.com/api-explorer/#/authentication
|
||||
|
||||
### Configuration
|
||||
***
|
||||
|
||||
### Performance Considerations
|
||||

|
||||
Describe Caching of tickets
|
||||
As mentioned in the *Security Considerations* section, Bcrypt can be used to encrypt passwords. Note that is
|
||||
is much slower than MD4 or SHA-256.
|
||||
***
|
||||
|
||||
### Security Considerations
|
||||
***
|
||||
In the case of the default Authentication provider, Alfresco is responsible for storing
|
||||
userid's and passwords. The passwords are stored as hashed values. The default hashing
|
||||
algorithm used is MD4, but the customer can also elect to use more secure hashing algorithms
|
||||
include SHA-256 and Bcrypt. The system property *system.preferred.password.encoding* is used
|
||||
to select which algorithm is used.
|
||||
|
||||
### Cloud Considerations
|
||||
|
||||

|
||||
Describe OAuth2 for protection of Cloud REST API's.
|
||||
***
|
After Width: | Height: | Size: 180 KiB |
@@ -0,0 +1,117 @@
|
||||
@startuml
|
||||
|
||||
Title: Default Authentication Login Flow
|
||||
|
||||
participant "Repository\nContainer" as W
|
||||
participant "LoginPost\nbean" as LB
|
||||
participant "SubsystemChaining\nAuthenticationService" as SCAS
|
||||
participant "MutableAuthentication\nServiceImpl" as AS
|
||||
participant "Authentication\nComponentImpl" as AC
|
||||
participant "PersonServiceImpl" as PS
|
||||
participant "NodeService" as NS
|
||||
participant "Authentication\nContextImpl" as ACX
|
||||
participant "Authentication\nUtil" as AU
|
||||
participant "InMemoryTicketComponentImpl" as ITC
|
||||
participant "AuthorityServiceImpl" as AUS
|
||||
participant "RepositoryAuthenticatedUser" as RAU
|
||||
participant "RepositoryAuthenticationDao" as RAD
|
||||
participant "MD4PasswordEncoderImpl" as MPE
|
||||
participant "RepositoryAuthenticationProvider" as RAP
|
||||
participant "CompositePasswordEncoder" as CPE
|
||||
|
||||
activate W
|
||||
W->LB: login request <username> <password>
|
||||
LB->LB: login <username> <password>
|
||||
LB->SCAS:authenticate <username> <password>
|
||||
note right of SCAS
|
||||
allowedUsers and maxUsers check
|
||||
end note
|
||||
SCAS->SCAS: preAuthenticationCheck
|
||||
note right of SCAS
|
||||
get list of usableAuthenticationServices
|
||||
and iterates over this list. In
|
||||
this default case there is a single
|
||||
service 'alfrescoNtlm'
|
||||
end note
|
||||
SCAS->AS: authenticate\n<username> <password>
|
||||
AS->AC:clearCurrentSecurityContext
|
||||
AC->ACX:clearCurrentSecurityContext
|
||||
ACX->AU:clearCurrentSecurityContext
|
||||
AU->ACX: done
|
||||
ACX->AC: done
|
||||
AC->AS: done
|
||||
AS->ITC:clearCurrentTicket
|
||||
ITC->AS: done
|
||||
note right of AS
|
||||
allowedUsers and maxUsers check
|
||||
end note
|
||||
AS->AS: preAuthenticationCheck
|
||||
note right of AS
|
||||
Checks cache to detect
|
||||
Brute Force attack
|
||||
end note
|
||||
AS->AS: isUserProtected <username>
|
||||
AS->AC: authenticate\n<username> <password>
|
||||
AC->PS:getUserIdentifier <username>
|
||||
PS->PS:getPersonOrNullImpl
|
||||
PS->PS:looks for Person\nin Cache
|
||||
PS->NS:get properties of Person\nand checks if\nneeds to add to cache
|
||||
PS->AC: username
|
||||
note right of AC
|
||||
Checks for guest
|
||||
end note
|
||||
AC->AC:isGuestUserName
|
||||
AC->AC: authenticateImpl <username> <password>
|
||||
AC->AU: getUserTenant
|
||||
AU->AC: tenant
|
||||
group Retrying Transaction
|
||||
AC->RAD: loadUserByUsername <username>
|
||||
RAD->RAU: constructor <username> <hashedpassword>
|
||||
RAD->AC: user details
|
||||
AC->RAP: isPasswordCorrect\n(UsernamePasswordAuthenticationToken, user details)
|
||||
RAP->CPE: matches
|
||||
CPE->MPE:isPasswordValid
|
||||
MPE->MPE:encodeInternal
|
||||
MPE->MPE: true
|
||||
MPE->RAP: true
|
||||
RAP->AC: true
|
||||
AC->AUS: isAdminAuthority
|
||||
AUS->AC: false
|
||||
AC->PS:getPersonOrNullImpl
|
||||
PS->PS:looks for Person\nin Cache
|
||||
PS->NS:get properties of Person\nand checks if\nneeds to add to cache
|
||||
NS->PS: properties
|
||||
PS->AC: username
|
||||
end
|
||||
group setCurrentUser <username>
|
||||
AC->ACX: isSystemUserName <username>
|
||||
ACX->AC: false
|
||||
AC->AC: setUserDetails (role=<b>ROLE_AUTHENTICATED</b>)
|
||||
end
|
||||
note right of AC
|
||||
Increment numberSuccessfulAuthentications
|
||||
end note
|
||||
AC->AC: onAuthenticate
|
||||
AC->AS: succeeded
|
||||
AS->ITC: clearCurrentTicket
|
||||
ITC->AS: done
|
||||
AS->ITC: getCurrentTicket (auto create off)
|
||||
ITC->AS
|
||||
AS->AS: getNewTicket
|
||||
AS->AS: preAuthenticationCheck
|
||||
AS->ITC: getNewTicket
|
||||
AS->SCAS
|
||||
SCAS->LB
|
||||
note right of LB
|
||||
Put ticket into the model
|
||||
end note
|
||||
LB->AS: getCurrentTicket
|
||||
AS->LB: ticket
|
||||
LB->W: Login Request Response
|
||||
note right of W
|
||||
Authentication TICKET
|
||||
returned in model
|
||||
end note
|
||||
deactivate W
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 383 KiB |
@@ -0,0 +1,310 @@
|
||||
@startuml
|
||||
|
||||
' Generated using https://github.com/juanmf/Java2PlantUML
|
||||
|
||||
left to right direction
|
||||
' Participants
|
||||
|
||||
interface org.alfresco.repo.security.authentication.AlfrescoSecureContext {
|
||||
--
|
||||
+ getEffectiveAuthentication() : Authentication
|
||||
+ getRealAuthentication() : Authentication
|
||||
+ setEffectiveAuthentication(i Authentication) : void
|
||||
+ setRealAuthentication(i Authentication) : void
|
||||
|
||||
}
|
||||
interface net.sf.acegisecurity.context.security.SecureContext {
|
||||
--
|
||||
+ getAuthentication() : Authentication
|
||||
+ setAuthentication(i Authentication) : void
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.AuthenticationUtil$ThreadLocalStack {
|
||||
--
|
||||
~ AuthenticationUtil$ThreadLocalStack()
|
||||
# initialValue() : Stack
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.TicketExpiredException {
|
||||
- serialVersionUID : long
|
||||
--
|
||||
+ TicketExpiredException(c String)
|
||||
+ TicketExpiredException(c String, c Throwable)
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$Ticket {
|
||||
- expires : c InMemoryTicketComponentImpl$ExpiryMode
|
||||
- expiryDate : c Date
|
||||
- serialVersionUID : long
|
||||
- testDuration : c Duration
|
||||
- ticketId : c String
|
||||
- userName : c String
|
||||
- validDuration : c Duration
|
||||
--
|
||||
- InMemoryTicketComponentImpl$Ticket(c InMemoryTicketComponentImpl$ExpiryMode, c Date, c String, c Duration, c String)
|
||||
~ InMemoryTicketComponentImpl$Ticket(c InMemoryTicketComponentImpl$ExpiryMode, c Date, c String, c Duration)
|
||||
# getExpires() : InMemoryTicketComponentImpl$ExpiryMode
|
||||
# getExpiryDate() : Date
|
||||
# getTicketId() : String
|
||||
# getUserName() : String
|
||||
+ equals(c Object) : boolean
|
||||
+ hashCode() : int
|
||||
~ getNewEntry() : InMemoryTicketComponentImpl$Ticket
|
||||
~ hasExpired(c Date) : boolean
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl {
|
||||
+ GRANTED_AUTHORITY_TICKET_PREFIX : c String
|
||||
- currentTicket : ThreadLocal< String>
|
||||
- expiryMode : c InMemoryTicketComponentImpl$ExpiryMode
|
||||
- guid : c String
|
||||
- oneOff : boolean
|
||||
- ticketsCache : SimpleCache< String, InMemoryTicketComponentImpl$Ticket>
|
||||
- ticketsExpire : boolean
|
||||
- useSingleTicketPerUser : boolean
|
||||
- validDuration : c Duration
|
||||
--
|
||||
+ InMemoryTicketComponentImpl()
|
||||
+ clearCurrentSecurityContext() : void
|
||||
+ clearCurrentTicket() : void
|
||||
+ countTickets(boolean) : int
|
||||
+ equals(c Object) : boolean
|
||||
+ getAuthorityForTicket(c String) : String
|
||||
+ getCurrentTicket(c String, boolean) : String
|
||||
+ getNewTicket(c String) : String
|
||||
+ getUseSingleTicketPerUser() : boolean
|
||||
+ getUsersWithTickets(boolean) : Set
|
||||
+ hashCode() : int
|
||||
+ invalidateTicketById(c String) : void
|
||||
+ invalidateTicketByUser(c String) : void
|
||||
+ invalidateTickets(boolean) : int
|
||||
+ setExpiryMode(c String) : void
|
||||
+ setOneOff(boolean) : void
|
||||
+ setTicketsCache( SimpleCache< String, InMemoryTicketComponentImpl$Ticket>) : void
|
||||
+ setTicketsExpire(boolean) : void
|
||||
+ setUseSingleTicketPerUser(boolean) : void
|
||||
+ setValidDuration(c String) : void
|
||||
+ validateTicket(c String) : String
|
||||
- findNonExpiredUserTicket(c String) : InMemoryTicketComponentImpl$Ticket
|
||||
- getTicketByTicketString(c String) : InMemoryTicketComponentImpl$Ticket
|
||||
- getTicketKey(c String) : String
|
||||
|
||||
}
|
||||
interface org.springframework.beans.factory.InitializingBean {
|
||||
--
|
||||
+ afterPropertiesSet() : void
|
||||
|
||||
}
|
||||
interface org.alfresco.repo.security.authentication.AuthenticationStep {
|
||||
--
|
||||
+ getArgs() : Object;
|
||||
+ getKey() : String
|
||||
+ getMessage() : String
|
||||
+ isSuccess() : boolean
|
||||
|
||||
}
|
||||
interface org.alfresco.repo.security.authentication.AuthenticationUtil$RunAsWork <Result extends c Object> {
|
||||
--
|
||||
+ doWork() : Object
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.AuthenticationUtil {
|
||||
+ SYSTEM_USER_NAME : c String
|
||||
- defaultAdminUserName : c String
|
||||
- defaultGuestUserName : c String
|
||||
- initialized : boolean
|
||||
- mtEnabled : boolean
|
||||
- threadLocalFullAuthenticationStack : ThreadLocal< Stack< Authentication>>
|
||||
- threadLocalRunAsAuthenticationStack : ThreadLocal< Stack< Authentication>>
|
||||
- threadLocalTenantDomainStack : ThreadLocal< Stack< String>>
|
||||
~ s_logger : i Log
|
||||
--
|
||||
+ AuthenticationUtil()
|
||||
+ afterPropertiesSet() : void
|
||||
+ clearCurrentSecurityContext() : void
|
||||
+ getAdminRoleName() : String
|
||||
+ getAdminUserName() : String
|
||||
+ getFullAuthentication() : Authentication
|
||||
+ getFullyAuthenticatedUser() : String
|
||||
+ getGuestRoleName() : String
|
||||
+ getGuestUserName() : String
|
||||
+ getRunAsAuthentication() : Authentication
|
||||
+ getRunAsUser() : String
|
||||
+ getSystemUserName() : String
|
||||
+ getUserTenant(c String) : Pair
|
||||
+ isMtEnabled() : boolean
|
||||
+ isRunAsUserTheSystemUser() : boolean
|
||||
+ logAuthenticatedUsers() : void
|
||||
+ logNDC(c String) : void
|
||||
+ popAuthentication() : void
|
||||
+ pushAuthentication() : void
|
||||
+ runAs( AuthenticationUtil$RunAsWork<R>, c String) : Object
|
||||
+ runAsSystem( AuthenticationUtil$RunAsWork<R>) : Object
|
||||
+ setAdminUserAsFullyAuthenticatedUser() : Authentication
|
||||
+ setDefaultAdminUserName(c String) : void
|
||||
+ setDefaultGuestUserName(c String) : void
|
||||
+ setFullAuthentication(i Authentication) : Authentication
|
||||
+ setFullyAuthenticatedUser(c String) : Authentication
|
||||
+ setMtEnabled(boolean) : void
|
||||
+ setRunAsUser(c String) : Authentication
|
||||
+ setRunAsUserSystem() : Authentication
|
||||
- getAuthenticationToken(c String, i UserDetails) : UsernamePasswordAuthenticationToken
|
||||
- getDefaultUserDetails(c String) : UserDetails
|
||||
- getUserName(i Authentication) : String
|
||||
- setFullyAuthenticatedUser(c String, i UserDetails) : Authentication
|
||||
~ setRunAsAuthentication(i Authentication) : Authentication
|
||||
~ setRunAsUser(c String, i UserDetails) : Authentication
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.AuthenticationException {
|
||||
- serialVersionUID : long
|
||||
~ diagnostic : c AuthenticationDiagnostic
|
||||
--
|
||||
+ AuthenticationException(c String)
|
||||
+ AuthenticationException(c String, c AuthenticationDiagnostic)
|
||||
+ AuthenticationException(c String, c AuthenticationDiagnostic, c Object;, c Throwable)
|
||||
+ AuthenticationException(c String, c AuthenticationDiagnostic, c Throwable)
|
||||
+ AuthenticationException(c String, c Object;)
|
||||
+ AuthenticationException(c String, c Object;, c AuthenticationDiagnostic)
|
||||
+ AuthenticationException(c String, c Object;, c Throwable)
|
||||
+ AuthenticationException(c String, c Throwable)
|
||||
+ getDiagnostic() : AuthenticationDiagnostic
|
||||
|
||||
}
|
||||
class org.alfresco.error.AlfrescoRuntimeException {
|
||||
- MESSAGE_DELIMITER : c String
|
||||
- errorCounter : c AtomicInteger
|
||||
- msgId : c String
|
||||
- msgParams : c Object;
|
||||
- serialVersionUID : long
|
||||
--
|
||||
+ AlfrescoRuntimeException(c String)
|
||||
+ AlfrescoRuntimeException(c String, c Object;)
|
||||
+ AlfrescoRuntimeException(c String, c Object;, c Throwable)
|
||||
+ AlfrescoRuntimeException(c String, c Throwable)
|
||||
+ create(c String, c Object;) : AlfrescoRuntimeException
|
||||
+ create(c Throwable, c String, c Object;) : AlfrescoRuntimeException
|
||||
+ getMsgId() : String
|
||||
+ getMsgParams() : Object;
|
||||
+ getNumericalId() : String
|
||||
+ getRootCause() : Throwable
|
||||
+ makeRuntimeException(c Throwable, c String, c Object;) : RuntimeException
|
||||
- buildErrorLogNumber(c String) : String
|
||||
- padInt(c StringBuilder, int, int) : void
|
||||
- resolveMessage(c String, c Object;) : String
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.AuthenticationDiagnostic {
|
||||
+ STEP_KEY_LDAP_AUTHENTICATION : c String
|
||||
+ STEP_KEY_LDAP_CONNECTED : c String
|
||||
+ STEP_KEY_LDAP_CONNECTING : c String
|
||||
+ STEP_KEY_LDAP_FORMAT_USER : c String
|
||||
+ STEP_KEY_LDAP_LOOKEDUP_USER : c String
|
||||
+ STEP_KEY_LDAP_LOOKUP_USER : c String
|
||||
+ STEP_KEY_LDAP_SEARCH : c String
|
||||
+ STEP_KEY_VALIDATION : c String
|
||||
+ STEP_KEY_VALIDATION_AUTHENTICATOR_NOT_ACTIVE : c String
|
||||
+ STEP_KEY_VALIDATION_AUTHENTICATOR_NOT_FOUND : c String
|
||||
- serialVersionUID : long
|
||||
- steps : List< AuthenticationStep>
|
||||
--
|
||||
+ AuthenticationDiagnostic()
|
||||
+ addStep(c String, boolean) : void
|
||||
+ addStep(c String, boolean, c Object;) : void
|
||||
+ addStep(i AuthenticationStep) : void
|
||||
+ getSteps() : List
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.AuthenticationStepImpl {
|
||||
- serialVersionUID : long
|
||||
~ args : c Object;
|
||||
~ key : c String
|
||||
~ success : boolean
|
||||
--
|
||||
+ AuthenticationStepImpl(c String)
|
||||
+ getArgs() : Object;
|
||||
+ getKey() : String
|
||||
+ getMessage() : String
|
||||
+ isSuccess() : boolean
|
||||
+ toString() : String
|
||||
|
||||
}
|
||||
interface net.sf.acegisecurity.context.Context {
|
||||
--
|
||||
+ validate() : void
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.authentication.AlfrescoSecureContextImpl {
|
||||
- effectiveAuthentication : i Authentication
|
||||
- realAuthentication : i Authentication
|
||||
- serialVersionUID : long
|
||||
--
|
||||
+ AlfrescoSecureContextImpl()
|
||||
+ equals(c Object) : boolean
|
||||
+ getAuthentication() : Authentication
|
||||
+ getEffectiveAuthentication() : Authentication
|
||||
+ getRealAuthentication() : Authentication
|
||||
+ hashCode() : int
|
||||
+ setAuthentication(i Authentication) : void
|
||||
+ setEffectiveAuthentication(i Authentication) : void
|
||||
+ setRealAuthentication(i Authentication) : void
|
||||
+ toString() : String
|
||||
+ validate() : void
|
||||
|
||||
}
|
||||
enum org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$ExpiryMode {
|
||||
+ AFTER_FIXED_TIME : c InMemoryTicketComponentImpl$ExpiryMode
|
||||
+ AFTER_INACTIVITY : c InMemoryTicketComponentImpl$ExpiryMode
|
||||
+ DO_NOT_EXPIRE : c InMemoryTicketComponentImpl$ExpiryMode
|
||||
--
|
||||
- InMemoryTicketComponentImpl$ExpiryMode()
|
||||
+ valueOf(c String) : InMemoryTicketComponentImpl$ExpiryMode
|
||||
+ values() : InMemoryTicketComponentImpl$ExpiryMode;
|
||||
|
||||
}
|
||||
interface org.alfresco.repo.security.authentication.TicketComponent {
|
||||
--
|
||||
+ clearCurrentTicket() : void
|
||||
+ countTickets(boolean) : int
|
||||
+ getAuthorityForTicket(c String) : String
|
||||
+ getCurrentTicket(c String, boolean) : String
|
||||
+ getNewTicket(c String) : String
|
||||
+ getUseSingleTicketPerUser() : boolean
|
||||
+ getUsersWithTickets(boolean) : Set
|
||||
+ invalidateTicketById(c String) : void
|
||||
+ invalidateTicketByUser(c String) : void
|
||||
+ invalidateTickets(boolean) : int
|
||||
+ validateTicket(c String) : String
|
||||
|
||||
}
|
||||
|
||||
' Relations
|
||||
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl "1" o-left- "1" org.alfresco.repo.cache.SimpleCache : ticketsCache: SimpleCache< String, InMemoryTicketComponentImpl$Ticket>
|
||||
org.alfresco.repo.security.authentication.AlfrescoSecureContextImpl "1" o-left- "1" net.sf.acegisecurity.Authentication : realAuthentication: i Authentication
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$Ticket "1" o-left- "1" org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$ExpiryMode : expires: c InMemoryTicketComponentImpl$ExpiryMode
|
||||
org.alfresco.error.AlfrescoRuntimeException "1" o-left- "1" java.util.concurrent.atomic.AtomicInteger : errorCounter: c AtomicInteger
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$Ticket "1" o-left- "1" org.alfresco.service.cmr.repository.datatype.Duration : testDuration: c Duration
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$Ticket "1" o-left- "1" org.alfresco.service.cmr.repository.datatype.Duration : validDuration: c Duration
|
||||
org.alfresco.repo.security.authentication.AuthenticationUtil "1" o-left- "1" org.apache.commons.logging.Log : s_logger: i Log
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl "1" o-left- "1" org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$ExpiryMode : expiryMode: c InMemoryTicketComponentImpl$ExpiryMode
|
||||
org.alfresco.repo.security.authentication.AuthenticationException "1" o-left- "1" org.alfresco.repo.security.authentication.AuthenticationDiagnostic : diagnostic: c AuthenticationDiagnostic
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$Ticket "1" o-left- "1" java.util.Date : expiryDate: c Date
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl "1" o-left- "1" org.alfresco.service.cmr.repository.datatype.Duration : validDuration: c Duration
|
||||
org.alfresco.repo.security.authentication.AlfrescoSecureContextImpl "1" o-left- "1" net.sf.acegisecurity.Authentication : effectiveAuthentication: i Authentication
|
||||
org.alfresco.repo.security.authentication.AuthenticationException -up|> org.alfresco.error.AlfrescoRuntimeException
|
||||
org.alfresco.repo.security.authentication.TicketExpiredException -up|> org.alfresco.repo.security.authentication.AuthenticationException
|
||||
"net.sf.acegisecurity.context.Context" -() Serializable
|
||||
"org.alfresco.repo.security.authentication.AuthenticationStepImpl" -() Serializable
|
||||
org.alfresco.repo.security.authentication.AlfrescoSecureContext ..up|> net.sf.acegisecurity.context.security.SecureContext
|
||||
org.alfresco.repo.security.authentication.AuthenticationStepImpl ..up|> org.alfresco.repo.security.authentication.AuthenticationStep
|
||||
org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl ..up|> org.alfresco.repo.security.authentication.TicketComponent
|
||||
net.sf.acegisecurity.context.security.SecureContext ..up|> net.sf.acegisecurity.context.Context
|
||||
"org.alfresco.repo.security.authentication.AuthenticationDiagnostic" -() Serializable
|
||||
"org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl$Ticket" -() Serializable
|
||||
org.alfresco.repo.security.authentication.AlfrescoSecureContextImpl ..up|> org.alfresco.repo.security.authentication.AlfrescoSecureContext
|
||||
org.alfresco.repo.security.authentication.AuthenticationUtil ..up|> org.springframework.beans.factory.InitializingBean
|
||||
|
||||
' Notes
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 369 KiB |
@@ -0,0 +1,344 @@
|
||||
@startuml
|
||||
|
||||
left to right direction
|
||||
|
||||
' Generated using https://github.com/juanmf/Java2PlantUML
|
||||
|
||||
' Participants
|
||||
|
||||
class org.alfresco.error.AlfrescoRuntimeException {
|
||||
- MESSAGE_DELIMITER : c String
|
||||
- errorCounter : c AtomicInteger
|
||||
- msgId : c String
|
||||
- msgParams : c Object;
|
||||
- serialVersionUID : long
|
||||
--
|
||||
+ AlfrescoRuntimeException(c String)
|
||||
+ AlfrescoRuntimeException(c String, c Object;)
|
||||
+ AlfrescoRuntimeException(c String, c Object;, c Throwable)
|
||||
+ AlfrescoRuntimeException(c String, c Throwable)
|
||||
+ create(c String, c Object;) : AlfrescoRuntimeException
|
||||
+ create(c Throwable, c String, c Object;) : AlfrescoRuntimeException
|
||||
+ getMsgId() : String
|
||||
+ getMsgParams() : Object;
|
||||
+ getNumericalId() : String
|
||||
+ getRootCause() : Throwable
|
||||
+ makeRuntimeException(c Throwable, c String, c Object;) : RuntimeException
|
||||
- buildErrorLogNumber(c String) : String
|
||||
- padInt(c StringBuilder, int, int) : void
|
||||
- resolveMessage(c String, c Object;) : String
|
||||
|
||||
}
|
||||
class org.alfresco.service.cmr.security.NoSuchPersonException {
|
||||
- serialVersionUID : long
|
||||
- userName : c String
|
||||
--
|
||||
+ NoSuchPersonException(c String)
|
||||
+ getUserName() : String
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.OwnableService {
|
||||
+ NO_OWNER : c String
|
||||
--
|
||||
+ getOwner(c NodeRef) : String
|
||||
+ hasOwner(c NodeRef) : boolean
|
||||
+ setOwner(c NodeRef, c String) : void
|
||||
+ takeOwnership(c NodeRef) : void
|
||||
|
||||
}
|
||||
class org.alfresco.service.cmr.security.PersonService$PersonInfo {
|
||||
- firstName : c String
|
||||
- lastName : c String
|
||||
- nodeRef : c NodeRef
|
||||
- userName : c String
|
||||
--
|
||||
+ PersonService$PersonInfo(c NodeRef, c String, c String, c String)
|
||||
+ getFirstName() : String
|
||||
+ getLastName() : String
|
||||
+ getNodeRef() : NodeRef
|
||||
+ getUserName() : String
|
||||
|
||||
}
|
||||
enum org.alfresco.service.cmr.security.AccessStatus {
|
||||
+ ALLOWED : c AccessStatus
|
||||
+ DENIED : c AccessStatus
|
||||
+ UNDETERMINED : c AccessStatus
|
||||
--
|
||||
- AccessStatus()
|
||||
+ valueOf(c String) : AccessStatus
|
||||
+ values() : AccessStatus;
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.AuthenticationService {
|
||||
--
|
||||
+ authenticate(c String, class [C) : void
|
||||
+ authenticateAsGuest() : void
|
||||
+ authenticationExists(c String) : boolean
|
||||
+ clearCurrentSecurityContext() : void
|
||||
+ getAuthenticationEnabled(c String) : boolean
|
||||
+ getCurrentTicket() : String
|
||||
+ getCurrentUserName() : String
|
||||
+ getDefaultAdministratorUserNames() : Set
|
||||
+ getDefaultGuestUserNames() : Set
|
||||
+ getDomains() : Set
|
||||
+ getDomainsThatAllowUserCreation() : Set
|
||||
+ getDomainsThatAllowUserDeletion() : Set
|
||||
+ getDomiansThatAllowUserPasswordChanges() : Set
|
||||
+ getNewTicket() : String
|
||||
+ guestUserAuthenticationAllowed() : boolean
|
||||
+ invalidateTicket(c String) : void
|
||||
+ invalidateUserSession(c String) : void
|
||||
+ isCurrentUserTheSystemUser() : boolean
|
||||
+ validate(c String) : void
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.AuthorityService {
|
||||
+ ZONE_APP_DEFAULT : c String
|
||||
+ ZONE_APP_SHARE : c String
|
||||
+ ZONE_AUTH_ALFRESCO : c String
|
||||
+ ZONE_AUTH_EXT_PREFIX : c String
|
||||
--
|
||||
+ addAuthority( Collection< String>, c String) : void
|
||||
+ addAuthority(c String, c String) : void
|
||||
+ addAuthorityToZones(c String, Set< String>) : void
|
||||
+ authorityExists(c String) : boolean
|
||||
+ countGroups() : long
|
||||
+ countUsers() : long
|
||||
+ createAuthority(c AuthorityType, c String) : String
|
||||
+ createAuthority(c AuthorityType, c String, c String, Set< String>) : String
|
||||
+ deleteAuthority(c String) : void
|
||||
+ deleteAuthority(c String, boolean) : void
|
||||
+ findAuthorities(c AuthorityType, c String, boolean, c String, c String) : Set
|
||||
+ getAllAuthorities(c AuthorityType) : Set
|
||||
+ getAllAuthoritiesInZone(c String, c AuthorityType) : Set
|
||||
+ getAllRootAuthorities(c AuthorityType) : Set
|
||||
+ getAllRootAuthoritiesInZone(c String, c AuthorityType) : Set
|
||||
+ getAuthorities() : Set
|
||||
+ getAuthorities(c AuthorityType, c String, c String, boolean, boolean, c PagingRequest) : PagingResults
|
||||
+ getAuthoritiesForUser(c String) : Set
|
||||
+ getAuthoritiesInfo(c AuthorityType, c String, c String, c String, boolean, c PagingRequest) : PagingResults
|
||||
+ getAuthorityDisplayName(c String) : String
|
||||
+ getAuthorityNodeRef(c String) : NodeRef
|
||||
+ getAuthorityZones(c String) : Set
|
||||
+ getContainedAuthorities(c AuthorityType, c String, boolean) : Set
|
||||
+ getContainingAuthorities(c AuthorityType, c String, boolean) : Set
|
||||
+ getContainingAuthoritiesInZone(c AuthorityType, c String, c String, i AuthorityService$AuthorityFilter, int) : Set
|
||||
+ getDefaultZones() : Set
|
||||
+ getName(c AuthorityType, c String) : String
|
||||
+ getOrCreateZone(c String) : NodeRef
|
||||
+ getShortName(c String) : String
|
||||
+ getZone(c String) : NodeRef
|
||||
+ hasAdminAuthority() : boolean
|
||||
+ hasGuestAuthority() : boolean
|
||||
+ isAdminAuthority(c String) : boolean
|
||||
+ isGuestAuthority(c String) : boolean
|
||||
+ removeAuthority(c String, c String) : void
|
||||
+ removeAuthorityFromZones(c String, Set< String>) : void
|
||||
+ setAuthorityDisplayName(c String, c String) : void
|
||||
|
||||
}
|
||||
enum org.alfresco.service.cmr.security.AuthorityType {
|
||||
+ ADMIN : c AuthorityType
|
||||
+ EVERYONE : c AuthorityType
|
||||
+ GROUP : c AuthorityType
|
||||
+ GUEST : c AuthorityType
|
||||
+ OWNER : c AuthorityType
|
||||
+ ROLE : c AuthorityType
|
||||
+ USER : c AuthorityType
|
||||
+ WILDCARD : c AuthorityType
|
||||
--
|
||||
- AuthorityType()
|
||||
+ equals(c String) : boolean
|
||||
+ getAuthorityType(c String) : AuthorityType
|
||||
+ getFixedString() : String
|
||||
+ getOrderPosition() : int
|
||||
+ getPrefixString() : String
|
||||
+ isFixedString() : boolean
|
||||
+ isPrefixed() : boolean
|
||||
+ valueOf(c String) : AuthorityType
|
||||
+ values() : AuthorityType;
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.PersonService {
|
||||
--
|
||||
+ countPeople() : int
|
||||
+ createMissingPeople() : boolean
|
||||
+ createPerson( Map< QName, Serializable>) : NodeRef
|
||||
+ createPerson( Map< QName, Serializable>, Set< String>) : NodeRef
|
||||
+ deletePerson(c NodeRef) : void
|
||||
+ deletePerson(c NodeRef, boolean) : void
|
||||
+ deletePerson(c String) : void
|
||||
+ getAllPeople() : Set
|
||||
+ getMutableProperties() : Set
|
||||
+ getPeople( List< Pair< QName, String>>, boolean, List< Pair< QName, Boolean>>, c PagingRequest) : PagingResults
|
||||
+ getPeople(c String, List< QName>, List< Pair< QName, Boolean>>, c PagingRequest) : PagingResults
|
||||
+ getPeople(c String, List< QName>, Set< QName>, Set< QName>, boolean, List< Pair< QName, Boolean>>, c PagingRequest) : PagingResults
|
||||
+ getPeopleContainer() : NodeRef
|
||||
+ getPeopleFilteredByProperty(c QName, i Serializable, int) : Set
|
||||
+ getPerson(c NodeRef) : PersonService$PersonInfo
|
||||
+ getPerson(c String) : NodeRef
|
||||
+ getPerson(c String, boolean) : NodeRef
|
||||
+ getPersonOrNull(c String) : NodeRef
|
||||
+ getUserIdentifier(c String) : String
|
||||
+ getUserNamesAreCaseSensitive() : boolean
|
||||
+ isEnabled(c String) : boolean
|
||||
+ isMutable() : boolean
|
||||
+ notifyPerson(c String, c String) : void
|
||||
+ personExists(c String) : boolean
|
||||
+ setCreateMissingPeople(boolean) : void
|
||||
+ setPersonProperties(c String, Map< QName, Serializable>) : void
|
||||
+ setPersonProperties(c String, Map< QName, Serializable>, boolean) : void
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.PublicServiceAccessService {
|
||||
--
|
||||
+ hasAccess(c String, c String, c Object;) : AccessStatus
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.MutableAuthenticationService {
|
||||
--
|
||||
+ createAuthentication(c String, class [C) : void
|
||||
+ deleteAuthentication(c String) : void
|
||||
+ isAuthenticationCreationAllowed() : boolean
|
||||
+ isAuthenticationMutable(c String) : boolean
|
||||
+ setAuthentication(c String, class [C) : void
|
||||
+ setAuthenticationEnabled(c String, boolean) : void
|
||||
+ updateAuthentication(c String, class [C, class [C) : void
|
||||
|
||||
}
|
||||
class org.alfresco.service.cmr.security.PermissionContext {
|
||||
- additionalContext : Map< String, Object>
|
||||
- aspects : HashSet< QName>
|
||||
- dynamicAuthorityAssignment : Map< String, Set< String>>
|
||||
- properties : Map< QName, Serializable>
|
||||
- storeAcl : c Long
|
||||
- type : c QName
|
||||
--
|
||||
+ PermissionContext(c QName)
|
||||
+ addDynamicAuthorityAssignment(c String, c String) : void
|
||||
+ getAdditionalContext() : Map
|
||||
+ getAspects() : HashSet
|
||||
+ getDynamicAuthorityAssignment() : Map
|
||||
+ getProperties() : Map
|
||||
+ getStoreAcl() : Long
|
||||
+ getType() : QName
|
||||
+ setStoreAcl(c Long) : void
|
||||
|
||||
}
|
||||
interface org.alfresco.repo.security.permissions.PermissionCheckValue {
|
||||
--
|
||||
+ getNodeRef() : NodeRef
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.PermissionService {
|
||||
+ ADD_CHILDREN : c String
|
||||
+ ADMINISTRATOR_AUTHORITY : c String
|
||||
+ ALL_AUTHORITIES : c String
|
||||
+ ALL_PERMISSIONS : c String
|
||||
+ ASPECTS : c String
|
||||
+ CANCEL_CHECK_OUT : c String
|
||||
+ CHANGE_PERMISSIONS : c String
|
||||
+ CHECK_IN : c String
|
||||
+ CHECK_OUT : c String
|
||||
+ CONSUMER : c String
|
||||
+ CONTRIBUTOR : c String
|
||||
+ COORDINATOR : c String
|
||||
+ CREATE_ASSOCIATIONS : c String
|
||||
+ CREATE_CHILDREN : c String
|
||||
+ DELETE : c String
|
||||
+ DELETE_ASSOCIATIONS : c String
|
||||
+ DELETE_CHILDREN : c String
|
||||
+ DELETE_NODE : c String
|
||||
+ EDITOR : c String
|
||||
+ EXECUTE : c String
|
||||
+ EXECUTE_CONTENT : c String
|
||||
+ FULL_CONTROL : c String
|
||||
+ GROUP_PREFIX : c String
|
||||
+ GUEST_AUTHORITY : c String
|
||||
+ LINK_CHILDREN : c String
|
||||
+ LOCK : c String
|
||||
+ LOCK_OWNER_AUTHORITY : c String
|
||||
+ OWNER_AUTHORITY : c String
|
||||
+ PROPERTIES : c String
|
||||
+ READ : c String
|
||||
+ READ_ASSOCIATIONS : c String
|
||||
+ READ_CHILDREN : c String
|
||||
+ READ_CONTENT : c String
|
||||
+ READ_PERMISSIONS : c String
|
||||
+ READ_PROPERTIES : c String
|
||||
+ ROLE_PREFIX : c String
|
||||
+ SET_OWNER : c String
|
||||
+ TAKE_OWNERSHIP : c String
|
||||
+ UNLOCK : c String
|
||||
+ WRITE : c String
|
||||
+ WRITE_CONTENT : c String
|
||||
+ WRITE_PROPERTIES : c String
|
||||
--
|
||||
+ clearPermission(c NodeRef, c String) : void
|
||||
+ clearPermission(c StoreRef, c String) : void
|
||||
+ deletePermission(c NodeRef, c String, c String) : void
|
||||
+ deletePermission(c StoreRef, c String, c String) : void
|
||||
+ deletePermissions(c NodeRef) : void
|
||||
+ deletePermissions(c StoreRef) : void
|
||||
+ getAllAuthorities() : String
|
||||
+ getAllPermission() : String
|
||||
+ getAllSetPermissions(c NodeRef) : Set
|
||||
+ getAllSetPermissions(c StoreRef) : Set
|
||||
+ getAuthorisations() : Set
|
||||
+ getInheritParentPermissions(c NodeRef) : boolean
|
||||
+ getOwnerAuthority() : String
|
||||
+ getPermissions(c NodeRef) : Set
|
||||
+ getReaders(c Long) : Set
|
||||
+ getReadersDenied(c Long) : Set
|
||||
+ getSettablePermissions(c NodeRef) : Set
|
||||
+ getSettablePermissions(c QName) : Set
|
||||
+ hasPermission(c Long, c PermissionContext, c String) : AccessStatus
|
||||
+ hasPermission(c NodeRef, c String) : AccessStatus
|
||||
+ hasReadPermission(c NodeRef) : AccessStatus
|
||||
+ setInheritParentPermissions(c NodeRef, boolean) : void
|
||||
+ setInheritParentPermissions(c NodeRef, boolean, boolean) : void
|
||||
+ setPermission(c NodeRef, c String, c String, boolean) : void
|
||||
+ setPermission(c StoreRef, c String, c String, boolean) : void
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.AuthorityService$AuthorityFilter {
|
||||
--
|
||||
+ includeAuthority(c String) : boolean
|
||||
|
||||
}
|
||||
interface org.alfresco.service.cmr.security.AccessPermission {
|
||||
--
|
||||
+ getAccessStatus() : AccessStatus
|
||||
+ getAuthority() : String
|
||||
+ getAuthorityType() : AuthorityType
|
||||
+ getPermission() : String
|
||||
+ getPosition() : int
|
||||
+ isInherited() : boolean
|
||||
+ isSetDirectly() : boolean
|
||||
|
||||
}
|
||||
class org.alfresco.repo.security.person.PersonException {
|
||||
- serialVersionUID : long
|
||||
--
|
||||
+ PersonException(c String)
|
||||
+ PersonException(c String, c Object;)
|
||||
+ PersonException(c String, c Object;, c Throwable)
|
||||
+ PersonException(c String, c Throwable)
|
||||
|
||||
}
|
||||
|
||||
' Relations
|
||||
|
||||
org.alfresco.service.cmr.security.PersonService$PersonInfo "1" o-left- "1" org.alfresco.service.cmr.repository.NodeRef : nodeRef: c NodeRef
|
||||
org.alfresco.error.AlfrescoRuntimeException "1" o-left- "1" java.util.concurrent.atomic.AtomicInteger : errorCounter: c AtomicInteger
|
||||
org.alfresco.service.cmr.security.PermissionContext "1" o-left- "*" org.alfresco.service.namespace.QName : aspects: HashSet< QName>
|
||||
org.alfresco.service.cmr.security.PermissionContext "1" o-left- "1" org.alfresco.service.namespace.QName : type: c QName
|
||||
org.alfresco.service.cmr.security.PermissionContext "1" o-left- "*" org.alfresco.service.namespace.QName : properties: Map< QName, Serializable>
|
||||
org.alfresco.repo.security.person.PersonException -up|> org.alfresco.error.AlfrescoRuntimeException
|
||||
org.alfresco.service.cmr.security.NoSuchPersonException -up|> org.alfresco.repo.security.person.PersonException
|
||||
org.alfresco.service.cmr.security.MutableAuthenticationService ..up|> org.alfresco.service.cmr.security.AuthenticationService
|
||||
org.alfresco.service.cmr.security.PersonService$PersonInfo ..up|> org.alfresco.repo.security.permissions.PermissionCheckValue
|
||||
|
||||
' Notes
|
||||
|
||||
@enduml
|
8
repository/docs/infrastructure/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Repository -> Infrastructure
|
||||
|
||||
## Sub-components
|
||||
|
||||
* [ ] [Module Framework](./module-framework)
|
||||
* [ ] [Cluster](./cluster)
|
||||
* [ ] [Policies and Behaviours](./policies-and-behaviours)
|
||||
* [ ] [Multi-tenancy](./multi-tenancy)
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,30 @@
|
||||
@startuml
|
||||
|
||||
title Tenant Service (aws)
|
||||
|
||||
|
||||
|
||||
interface TenantService
|
||||
interface TenantServiceSPI
|
||||
|
||||
class Tenant
|
||||
class TenantServiceDDBImpl
|
||||
class TenantServiceImpl
|
||||
class TenantServiceLambda
|
||||
|
||||
|
||||
Tenant <.. TenantService : uses
|
||||
Tenant <.. TenantServiceDDBImpl : uses
|
||||
Tenant <.. TenantServiceImpl : uses
|
||||
Tenant <.. TenantServiceSPI : uses
|
||||
TenantServiceDDBImpl <.. TenantServiceLambda : uses
|
||||
TenantServiceImpl <.. TenantServiceLambda : uses
|
||||
TenantServiceSPI <.. TenantServiceImpl : uses
|
||||
TenantServiceSPI <.. TenantServiceLambda : uses
|
||||
|
||||
TenantService <|.. TenantServiceImpl : implements
|
||||
TenantServiceSPI <|.. TenantServiceDDBImpl : implements
|
||||
|
||||
center footer © 2016 Alfresco Software Inc. all rights reserved \n Generated from PlantUML
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 68 KiB |
@@ -0,0 +1,89 @@
|
||||
@startuml
|
||||
left to right direction
|
||||
|
||||
|
||||
interface com.alfresco.services.lambda.TenantServiceSPI {
|
||||
--
|
||||
+ createTenant(c String, c String) : Tenant
|
||||
+ deleteTenant(c String) : boolean
|
||||
+ getTenant(c String) : Tenant
|
||||
+ getTenantSchema(c String) : String
|
||||
+ updateTenant(c Tenant) : boolean
|
||||
|
||||
}
|
||||
interface com.alfresco.services.lambda.TenantService {
|
||||
--
|
||||
+ createTenant(c String, c String) : Tenant
|
||||
+ deleteTenant(c String) : boolean
|
||||
+ getTenant(c String) : Tenant
|
||||
+ getTenantSchema(c String) : String
|
||||
+ updateTenant(c Tenant) : boolean
|
||||
|
||||
}
|
||||
class com.alfresco.services.lambda.TenantServiceDDBImpl {
|
||||
- regions : c Regions
|
||||
- tableName : c String
|
||||
--
|
||||
+ TenantServiceDDBImpl()
|
||||
+ createTenant(c String, c String) : Tenant
|
||||
+ deleteTenant(c String) : boolean
|
||||
+ getTenant(c String) : Tenant
|
||||
+ getTenantSchema(c String) : String
|
||||
+ updateTenant(c Tenant) : boolean
|
||||
- log(c String) : void
|
||||
|
||||
}
|
||||
class com.alfresco.services.lambda.TenantServiceLambda {
|
||||
--
|
||||
+ TenantServiceLambda()
|
||||
+ handleRequest(c Tenant, i Context) : Tenant
|
||||
|
||||
}
|
||||
class com.alfresco.services.lambda.TenantServiceImpl {
|
||||
- tenantServiceSPI : i TenantServiceSPI
|
||||
--
|
||||
+ TenantServiceImpl(i TenantServiceSPI)
|
||||
+ createTenant(c String, c String) : Tenant
|
||||
+ deleteTenant(c String) : boolean
|
||||
+ getTenant(c String) : Tenant
|
||||
+ getTenantSchema(c String) : String
|
||||
+ updateTenant(c Tenant) : boolean
|
||||
|
||||
}
|
||||
class com.alfresco.services.lambda.Tenant {
|
||||
- method : c String
|
||||
- schema : c String
|
||||
- spiProvider : c String
|
||||
- tenantId : c String
|
||||
- user : c String
|
||||
--
|
||||
+ Tenant()
|
||||
+ getMethod() : String
|
||||
+ getSchema() : String
|
||||
+ getSpiProvider() : String
|
||||
+ getTenantId() : String
|
||||
+ getUser() : String
|
||||
+ setMethod(c String) : void
|
||||
+ setSchema(c String) : void
|
||||
+ setSpiProvider(c String) : void
|
||||
+ setTenantId(c String) : void
|
||||
+ setUser(c String) : void
|
||||
|
||||
}
|
||||
interface com.amazonaws.services.lambda.runtime.RequestHandler <I extends c Object, O extends c Object> {
|
||||
--
|
||||
+ handleRequest(I, i Context) : Object
|
||||
|
||||
}
|
||||
|
||||
' Relations
|
||||
|
||||
com.alfresco.services.lambda.TenantServiceImpl "1" o-left- "1" com.alfresco.services.lambda.TenantServiceSPI : tenantServiceSPI: i TenantServiceSPI
|
||||
com.alfresco.services.lambda.TenantServiceDDBImpl "1" o-left- "1" com.amazonaws.regions.Regions : regions: c Regions
|
||||
com.alfresco.services.lambda.TenantServiceImpl ..up|> com.alfresco.services.lambda.TenantService
|
||||
com.alfresco.services.lambda.TenantServiceDDBImpl ..up|> com.alfresco.services.lambda.TenantServiceSPI
|
||||
com.alfresco.services.lambda.TenantServiceLambda ..up|> com.amazonaws.services.lambda.runtime.RequestHandler
|
||||
|
||||
' Notes
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,51 @@
|
||||
@startuml
|
||||
|
||||
title Tenant Component (nosql)
|
||||
|
||||
skinparam linetype ortho
|
||||
|
||||
interface ApiConstants
|
||||
interface TenantService
|
||||
interface TenantServiceSPI
|
||||
|
||||
class AbstractRestResource {
|
||||
#switchTenant()
|
||||
}
|
||||
class AbstractServiceClient
|
||||
class An2ApiException
|
||||
class CreateTenantPojo
|
||||
class Cx1TenantService
|
||||
class GetTenantPojo
|
||||
class InitializingBean
|
||||
class NoSuchTenantException
|
||||
class TenantExistsException
|
||||
class TenantService
|
||||
class TenantServiceClient
|
||||
class TenantServiceImpl
|
||||
class TenantServiceIT
|
||||
class TenantServiceRestV1
|
||||
class VersionCheckException
|
||||
|
||||
|
||||
AbstractRestResource <|-- TenantServiceRestV1 : extends
|
||||
AbstractServiceClient <|-- TenantServiceClient : extends
|
||||
An2ApiException <|-- NoSuchTenantException : extends
|
||||
An2ApiException <|-- TenantExistsException : extends
|
||||
An2ApiException <|-- VersionCheckException : extends
|
||||
ApiConstants <|.. TenantServiceImpl : uses
|
||||
CreateTenantPojo <.. TenantServiceClient : uses
|
||||
CreateTenantPojo <.. TenantServiceRestV1 : uses
|
||||
GetTenantPojo <.. TenantServiceClient : uses
|
||||
GetTenantPojo <.. TenantServiceRestV1 : uses
|
||||
InitializingBean <|.. TenantServiceImpl : implements
|
||||
NoSuchTenantException <.. AbstractRestResource : uses
|
||||
TenantService <.. AbstractRestResource : uses
|
||||
TenantService <|.. TenantServiceClient : implements
|
||||
TenantService <|.. TenantServiceImpl : implements
|
||||
TenantService <|.. TenantServiceRestV1 : uses
|
||||
TenantServiceClient <.. TenantServiceIT : uses
|
||||
TenantServiceSPI <.. Cx1TenantService : implements
|
||||
TenantServiceSPI <.. TenantServiceImpl : uses
|
||||
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,39 @@
|
||||
@startuml
|
||||
|
||||
title Tenant Service (nosql)
|
||||
|
||||
skinparam linetype ortho
|
||||
|
||||
node "Service\nClient" as SC #E0F2F1
|
||||
|
||||
node "API Gateway" as APIG #EEEEEE {
|
||||
|
||||
node "/tenants Resource" as TR #EFEBE9 {
|
||||
node "method POST" as POST #FBE9E7 {
|
||||
node "POST \nbody mapping template" #DCEDC8
|
||||
}
|
||||
node "method PUT" as PUT #FBE9E7{
|
||||
node "PUT \nbody mapping template" #DCEDC8
|
||||
}
|
||||
node "method GET" as GET #FBE9E7
|
||||
node "method DELETE" As DELETE #FBE9E7
|
||||
}
|
||||
}
|
||||
node "Tenant Service \nLambda Function" as L #FFD54F
|
||||
|
||||
database "DynamoDB Database\nalf-data-tenants" as DB #80D8FF
|
||||
|
||||
node "DynamoDB\nClient" As DDBC #B3E5FC
|
||||
|
||||
node "DynamoDB" As DDB #B2EBF2
|
||||
|
||||
SC->TR:https
|
||||
POST->L:JSON
|
||||
PUT->L:JSON
|
||||
GET->L:URL {tenant-id}
|
||||
DELETE->L:URL {tenant-id}
|
||||
L->DDBC
|
||||
DDBC->DDB:reads/writes
|
||||
DDB->DB:reads/writes
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,86 @@
|
||||
@startuml
|
||||
|
||||
Title Tenant Deployment\nDesign Options
|
||||
|
||||
skinparam nodeBorderColor black
|
||||
|
||||
left to right direction
|
||||
|
||||
node "AWS" as 2G #EEEEEE {
|
||||
node "Tenant\nService" as 2T #white
|
||||
node "Alfresco\nControl Architecture" as 2CA #white
|
||||
node "Customer A" as 2C1 #FFCDD2 {
|
||||
node "Alfresco 5.1" as 2A1 #E6EE9C {
|
||||
node "Tenant" as 2T1
|
||||
}
|
||||
}
|
||||
node "Customer B" as 2C2 #FFCDD2{
|
||||
node "Alfresco 5.1" as 2A2 #E6EE9C {
|
||||
node "Tenant" as 2T2
|
||||
}
|
||||
}
|
||||
node "Customer C" as 2C3 #FFCDD2{
|
||||
node "Alfresco 5.1" as 2A3 #E6EE9C {
|
||||
node "Tenant" as 2T3
|
||||
}
|
||||
}
|
||||
node "Customers D and E" as CDE #FFCDD2{
|
||||
node "Repository Next" as 2B #B3E5FC
|
||||
node "Tenant\n(Customer D)" as 2CD
|
||||
node "Tenant\n(Customer E)" as 2CE
|
||||
}
|
||||
}
|
||||
|
||||
2CA=>2C1
|
||||
2CA=>2C2
|
||||
2CA=>2C3
|
||||
2CA=>CDE
|
||||
2CA=>2T
|
||||
2T=>2CD
|
||||
2T=>2CE
|
||||
2B=>2T
|
||||
|
||||
node "AWS" as G #EEEEEE {
|
||||
node "Alfresco\nControl Architecture" as CA #white
|
||||
node "Customer A" as C1 #FFCDD2 {
|
||||
node "Alfresco 5.1" as A1 #E6EE9C {
|
||||
node "Tenant" as T1
|
||||
}
|
||||
}
|
||||
node "Customer B" as C2 #FFCDD2{
|
||||
node "Alfresco 5.1" as A2 #E6EE9C {
|
||||
node "Tenant" as T2
|
||||
}
|
||||
}
|
||||
node "Customer C" as C3 #FFCDD2{
|
||||
node "Alfresco 5.1" as A3 #E6EE9C {
|
||||
node "Tenant" as T3
|
||||
}
|
||||
}
|
||||
node "Customer D" as C4 #FFCDD2 {
|
||||
node "Tenant\nService" as TC1 #white
|
||||
node "Tenant" as T5
|
||||
node "Repository Next" as B1 #B3E5FC
|
||||
}
|
||||
node "Customer E" as C5 #FFCDD2 {
|
||||
node "Tenant\nService" as TC2 #white
|
||||
node "Tenant" as T6
|
||||
node "Tenant" as T7
|
||||
node "Tenant" as T8
|
||||
node "Repository Next" as B2 #B3E5FC
|
||||
}
|
||||
}
|
||||
|
||||
TC1=>T5
|
||||
TC2=>T6
|
||||
TC2=>T7
|
||||
TC2=>T8
|
||||
CA=>C1
|
||||
CA=>C2
|
||||
CA=>C3
|
||||
CA=>C4
|
||||
CA=>C5
|
||||
B2=>TC2
|
||||
B1=>TC1
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 77 KiB |
@@ -0,0 +1,69 @@
|
||||
@startuml
|
||||
|
||||
Title: Create a Tenant (NoSQL - AWS)
|
||||
actor "User" as U
|
||||
participant "Tenant Console" as TC
|
||||
participant "API Gateway" as APIG
|
||||
participant "TenantServiceLambda" as TSL
|
||||
participant "TenantServiceImpl" as TSI
|
||||
participant "DDbTenantService" as DDTS
|
||||
participant "Dynamo DB Client" as DDBC
|
||||
database "DynamoDB" as DDB
|
||||
|
||||
U->TC:create Tenant
|
||||
note right
|
||||
What the Tenant Console
|
||||
is is TBD. It
|
||||
may be part of the
|
||||
AWS Control Architecture
|
||||
or a Beowulf Admin
|
||||
Console
|
||||
end note
|
||||
activate TC
|
||||
TC->APIG: HTTP POST {<API Gateway host:port>/public/an2/v1/tenants\nTenant Object(JSON)
|
||||
activate APIG
|
||||
APIG->TSL: handler(serialized Tenant Object(JSON))
|
||||
note right
|
||||
The HTTP Verb and UserInfo is
|
||||
passed to the Tenant Service
|
||||
Lambda in the JSON object.
|
||||
Tenant Service Lambda selects
|
||||
the TenantServiceImpl method
|
||||
to call based on the HTTP Verb
|
||||
end note
|
||||
activate TSL
|
||||
TSL->TSI: createTenant()
|
||||
note right
|
||||
Unsolved Design Challenge #1:
|
||||
How to supply a configuration
|
||||
to allow alternative
|
||||
implementations at
|
||||
runtime. In current
|
||||
form, the TenantServiceSPI
|
||||
passed to TenantServiceImpl
|
||||
is set in an API Gateway
|
||||
template. That would allow
|
||||
us to specify a different
|
||||
provider of the SPI using,
|
||||
for example, an HTTP parm
|
||||
end note
|
||||
activate TSI
|
||||
TSI->DDTS
|
||||
activate DDTS
|
||||
DDTS->DDBC: table.putItem(new Item().withPrimaryKey("tenant-id")
|
||||
activate DDBC
|
||||
DDBC->DDB: store the tenant record
|
||||
DDB->DDBC: success
|
||||
deactivate DDB
|
||||
DDBC->DDTS:success
|
||||
deactivate DDBC
|
||||
DDTS->TSI:success
|
||||
deactivate DDTS
|
||||
TSI->TSL: success
|
||||
deactivate TSI
|
||||
TSL->TC: HTTP 200 OK
|
||||
deactivate TSL
|
||||
TC->>U:success message
|
||||
deactivate TC
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 60 KiB |
@@ -0,0 +1,60 @@
|
||||
@startuml
|
||||
|
||||
Title: Create a Tenant (NoSQL)
|
||||
actor "User" as U
|
||||
participant "Tenant Console" as TC
|
||||
participant "TenantServiceClient" as TCL
|
||||
participant "TenantServiceRestV1" as TRS
|
||||
participant "TenantServiceImpl" as TSI
|
||||
participant "Cx1TenantService" as CTS
|
||||
participant "cassandra-driver-core-2.2.0-rc1" as DDC
|
||||
database "Cassandra" as C
|
||||
|
||||
U->TC:create Tenant
|
||||
note right
|
||||
The Tenant Console
|
||||
may be part of the
|
||||
Admin Console
|
||||
end note
|
||||
activate TC
|
||||
TC->TCL:createTenant()
|
||||
activate TCL
|
||||
TCL->TCL: find server from super\nAbstractServiceClient
|
||||
TCL->TRS: HTTP POST {tenantCtx}/public/an2/v1/tenants\nCreateTenantPojo(JSON)
|
||||
activate TRS
|
||||
note right
|
||||
This is wired
|
||||
to the endpoint
|
||||
using Jersey
|
||||
end note
|
||||
TRS->TSI: create(TenantServiceSPI=Cx1TenantService)
|
||||
activate TSI
|
||||
TSI->CTS: createTenant()
|
||||
note right
|
||||
The Cassandra
|
||||
implementation can
|
||||
be replaced by
|
||||
other implementations
|
||||
(e.g. Dynamo DB)
|
||||
through Spring config
|
||||
end note
|
||||
activate CTS
|
||||
CTS->DDC: insert into Table alf_data_tenants
|
||||
activate DDC
|
||||
DDC->C: store the tenant record
|
||||
C->DDC:success
|
||||
deactivate C
|
||||
DDC->CTS: success
|
||||
deactivate DDC
|
||||
CTS->TSI:success
|
||||
deactivate CTS
|
||||
TSI->TRS: success
|
||||
deactivate TSI
|
||||
TRS->TCL: HTTP 200 OK
|
||||
deactivate TRS
|
||||
TCL->TC: success
|
||||
deactivate TCL
|
||||
TC->>U:success message
|
||||
deactivate TC
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,175 @@
|
||||
@startuml
|
||||
|
||||
title Policies: Policy Component (V4.2 onwards)
|
||||
|
||||
' Split into 2 pages
|
||||
page 2x1
|
||||
|
||||
interface PolicyEndpointService #DDDDDD
|
||||
class PolicyEndpointServiceImpl #DDDDDD implements PolicyEndpointService{
|
||||
- policyComponent : PolicyComponent
|
||||
- policyEndpointRegistry : registry
|
||||
+ registerPolicyEndpoint(QName policy, String endpoint) : void
|
||||
+ registerPolicyEndpoint(QName policy, QName typeOrAspect, String endpoint) : void
|
||||
+ unregisterPolicyEndpoint(QName policy, String endpoint) : void
|
||||
+ unregisterPolicyEndpoint(QName policy, QName typeOrAspect, String endpoint) : void
|
||||
}
|
||||
PolicyEndpointServiceImpl -> PolicyComponent
|
||||
class PolicyEndpointRegistry #DDDDDD {
|
||||
- QueuedBehaviour dynamicBehaviour
|
||||
- Set<PolicyEndPointPolicyEndpoint> registrations
|
||||
+ {static} PolicyEndpointRegistry getInstance()
|
||||
+ reload(long fromTime)
|
||||
+ slip(String body, @Properties Map<String, Object> properties)
|
||||
}
|
||||
PolicyEndpointRegistry *- "1" QueuedBehaviour
|
||||
PolicyEndpointServiceImpl -> PolicyEndpointRegistry
|
||||
class PolicyEndpoint <<immutable>> #DDDDDD {
|
||||
- QName policy
|
||||
- QName typeOrAspect
|
||||
- String endpoint
|
||||
- long modified
|
||||
- boolean active
|
||||
+ getKey()
|
||||
}
|
||||
PolicyEndpointRegistry *- PolicyEndpoint
|
||||
|
||||
|
||||
interface Policy
|
||||
interface ClassPolicy extends Policy
|
||||
interface OnCreateNodePolicy extends ClassPolicy {
|
||||
+QNAME: http://www.alfresco.org:onCreateNode
|
||||
+onCreateNode(ChildAssociationRef childAssocRef)
|
||||
}
|
||||
interface AssociationPolicy extends Policy
|
||||
interface OnCreateChildAssociationPolicy extends AssociationPolicy {
|
||||
+QNAME: http://www.alfresco.org:onCreateChildAssociation
|
||||
+onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode)
|
||||
}
|
||||
|
||||
enum NotificationFrequency {
|
||||
EVERY_EVENT,
|
||||
FIRST_EVENT,
|
||||
TRANSACTION_COMMIT
|
||||
}
|
||||
|
||||
interface Behaviour {
|
||||
+ <T> getInterface(Class<T> policy) : T
|
||||
+ disable()
|
||||
+ enable()
|
||||
+ isEnabled() : boolean
|
||||
+ getNotificationFrequency() : NotificationFrequency
|
||||
}
|
||||
class BaseBehaviour implements Behaviour {
|
||||
# proxies : Map<Class, Object>
|
||||
# frequency : NotificationFrequency
|
||||
- disabled : StackThreadLocal
|
||||
+ disable()
|
||||
+ enable()
|
||||
+ isEnabled() : boolean
|
||||
+ getNotificationFrequency() : NotificationFrequency
|
||||
}
|
||||
class JavaBehaviour extends BaseBehaviour {
|
||||
+ <T> getInterface(Class<T> policy) : T
|
||||
# <T> getInvocationHandler(Object instance, String method, Class<T> policyIF) : InvocationHandler
|
||||
}
|
||||
class QueuedBehaviour <<V5.2>> #DDDDDD extends BaseBehaviour {
|
||||
- ProducerTemplate queueTemplate
|
||||
+ <T> getInterface(Class<T> policy) : T
|
||||
# <T> getInvocationHandler(Object instance, String method, Class<T> policyIF) : InvocationHandler
|
||||
}
|
||||
class ScriptBehaviour extends BaseBehaviour
|
||||
BaseBehaviour o- NotificationFrequency
|
||||
|
||||
interface BehaviourBinding {
|
||||
+ generaliseBinding() : BehaviourBinding
|
||||
}
|
||||
class ClassBehaviourBinding implements BehaviourBinding {
|
||||
+ generaliseBinding() : BehaviourBinding
|
||||
}
|
||||
class ClassFeatureBehaviourBinding extends ClassBehaviourBinding {
|
||||
}
|
||||
class ServiceBehaviourBinding implements BehaviourBinding {
|
||||
+ generaliseBinding() : BehaviourBinding
|
||||
}
|
||||
|
||||
class ClassPolicyDelegate<P extends ClassPolicy> {
|
||||
- dictionary : DictionaryService
|
||||
- factory : CachedPolicyFactory<ClassFeatureBehaviourBinding, P>
|
||||
# ClassPolicyDelegate(DictionaryService dictionary, Class<P> policyClass, BehaviourIndex<ClassBehaviourBinding> index, long tryLockTimeout)
|
||||
}
|
||||
class AssociationPolicyDelegate<P extends AssociationPolicy> {
|
||||
- dictionary : DictionaryService
|
||||
- factory : CachedPolicyFactory<ClassFeatureBehaviourBinding, P>
|
||||
# AssociationPolicyDelegate(DictionaryService dictionary, Class<P> policyClass, BehaviourIndex<ClassFeatureBehaviourBinding> index, long tryLockTimeout)
|
||||
}
|
||||
class PropertyPolicyDelegate<P extends PropertyPolicy> {
|
||||
- dictionary : DictionaryService
|
||||
- factory : CachedPolicyFactory<ClassFeatureBehaviourBinding, P>
|
||||
# PropertyPolicyDelegate(DictionaryService dictionary, Class<P> policyClass, BehaviourIndex<ClassFeatureBehaviourBinding> index, long tryLockTimeout)
|
||||
}
|
||||
class PolicyFactory<B extends BehaviourBinding, P extends Policy> {
|
||||
- index : BehaviourIndex~<B>
|
||||
- policyClass : Class~<P>
|
||||
- transactionHandlerFactory : TransactionInvocationHandlerFactory
|
||||
- tenantService : TenantService
|
||||
--
|
||||
PolicyFactory(Class<P> policyClass, BehaviourIndex~<B> index)
|
||||
--
|
||||
}
|
||||
class CachedPolicyFactory<B extends BehaviourBinding, P extends Policy> extends PolicyFactory
|
||||
|
||||
abstract class AbstractNodeServiceImpl implements NodeService {
|
||||
- policyComponent : PolicyComponent
|
||||
# dictionaryService : DictionaryService
|
||||
# transactionService : TransactionService
|
||||
# tenantService : TenantService
|
||||
- onCreateNodeDelegate : ClassPolicyDelegate<OnCreateNodePolicy>
|
||||
}
|
||||
interface PolicyComponent {
|
||||
+ <P extends ClassPolicy> registerClassPolicy(Class<P> policy) : ClassPolicyDelegate<P>
|
||||
+ <P extends PropertyPolicy> registerPropertyPolicy(Class<P> policy) : PropertyPolicyDelegate<P>
|
||||
+ <P extends AssociationPolicy> registerAssociationPolicy(Class<P> policy) : AssociationPolicyDelegate<P>
|
||||
+ bindClassBehaviour(QName policy, QName className, Behaviour behaviour) : BehaviourDefinition<ClassBehaviourBinding>
|
||||
+ bindClassBehaviour(QName policy, Object service, Behaviour behaviour) : BehaviourDefinition<ServiceBehaviourBinding>
|
||||
+ bindPropertyBehaviour(QName policy, QName className, QName propertyName, Behaviour behaviour) : BehaviourDefinition<ClassFeatureBehaviourBinding>
|
||||
+ bindPropertyBehaviour(QName policy, QName className, Behaviour behaviour) : BehaviourDefinition<ClassFeatureBehaviourBinding>
|
||||
+ bindPropertyBehaviour(QName policy, Object service, Behaviour behaviour) : BehaviourDefinition<ServiceBehaviourBinding>
|
||||
+ bindAssociationBehaviour(QName policy, QName className, QName assocName, Behaviour behaviour) : BehaviourDefinition<ClassFeatureBehaviourBinding>
|
||||
+ bindAssociationBehaviour(QName policy, QName className, Behaviour behaviour) : BehaviourDefinition<ClassFeatureBehaviourBinding>
|
||||
+ bindAssociationBehaviour(QName policy, Object service, Behaviour behaviour) : BehaviourDefinition<ServiceBehaviourBinding>
|
||||
+ removeClassDefinition(BehaviourDefinition<ClassBehaviourBinding> definition) : void
|
||||
}
|
||||
class PolicyComponentImpl implements PolicyComponent {
|
||||
- Map<QName, ClassBehaviourIndex<ClassBehaviourBinding>> classBehaviours
|
||||
- Map<QName, ClassBehaviourIndex<ClassFeatureBehaviourBinding>> propertyBehaviours
|
||||
- Map<QName, ClassBehaviourIndex<ClassFeatureBehaviourBinding>> associationBehaviours
|
||||
+ PolicyComponentImpl(DictionaryService dictionary)
|
||||
+ ... (all)
|
||||
}
|
||||
interface BehaviourFilter {
|
||||
+ disableBehaviour() : void
|
||||
+ disableBehaviour(QName className) : void
|
||||
+ disableBehaviour(QName className, boolean includeSubClasses) : void
|
||||
+ disableBehaviour(NodeRef nodeRef, QName className) : void
|
||||
+ disableBehaviour(NodeRef nodeRef) : void
|
||||
+ enableBehaviour() : void
|
||||
+ enableBehaviour(QName className) : void
|
||||
+ enableBehaviour(NodeRef nodeRef, QName className) : void
|
||||
+ enableBehaviour(NodeRef nodeRef) : void
|
||||
}
|
||||
class BehaviourFilterImpl implements BehaviourFilter {
|
||||
+ ... (all)
|
||||
}
|
||||
|
||||
AbstractNodeServiceImpl -> PolicyComponentImpl
|
||||
PolicyComponentImpl -> BehaviourFilterImpl
|
||||
PolicyComponentImpl -> "creates" ClassPolicyDelegate
|
||||
PolicyComponentImpl -> "creates" AssocationPolicyDelegate
|
||||
PolicyComponentImpl -> "creates" PropertyPolicyDelegate
|
||||
ClassPolicyDelegate --> "uses" PolicyFactory
|
||||
AssociationPolicyDelegate --> "uses" PolicyFactory
|
||||
PropertyPolicyDelegate --> "uses" PolicyFactory
|
||||
PolicyFactory --> "creates" BehaviourBinding
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,56 @@
|
||||
@startuml
|
||||
|
||||
title Policies: Queued Policies (V5.2 Proposal)
|
||||
|
||||
actor user
|
||||
|
||||
database ActiveMQ as AMQ
|
||||
database DB {
|
||||
folder alf_policies
|
||||
}
|
||||
|
||||
component "Client Application" {
|
||||
component [Client Code]
|
||||
component [ClientQueueReceiver]
|
||||
component [Camel(Client App)]
|
||||
}
|
||||
user <--> [Client Code]
|
||||
ClientQueueReceiver <-- [Camel(Client App)]
|
||||
ClientQueueReceiver -> [Client Code]
|
||||
[Camel(Client App)] <- AMQ
|
||||
|
||||
component "Alfresco One Platform" {
|
||||
component Quartz as Q
|
||||
component [Camel] as Camel
|
||||
|
||||
component [Policy ReST API] as PR
|
||||
component PolicyService as PS {
|
||||
[PolicyCheckJob] as PCJ
|
||||
[PolicyMap] as PM <<Map>>
|
||||
component Routes as R {
|
||||
component ToQueue
|
||||
component FromQueue
|
||||
}
|
||||
}
|
||||
component PolicyComponent as PC {
|
||||
component QueuedBehaviour as QB
|
||||
}
|
||||
component [Alfresco Services] as Services
|
||||
component PolicyDAO as PD
|
||||
}
|
||||
|
||||
[Client Code] <-> PR
|
||||
|
||||
Q --> PCJ
|
||||
PCJ -> PM
|
||||
PR --> PS
|
||||
Services -> PC
|
||||
PCJ --> PD
|
||||
PD -> DB
|
||||
QB -> ToQueue
|
||||
ToQueue -> Camel
|
||||
FromQueue <- Camel
|
||||
FromQueue --> Services
|
||||
Camel <-> AMQ
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,37 @@
|
||||
@startuml
|
||||
|
||||
Title: Policies: Register Policy Endpoint (V5.2 Proposal)
|
||||
|
||||
skinparam componentStyle uml2
|
||||
|
||||
actor "Client App" as Client
|
||||
participant "Policy Endpoint Rest API" as API
|
||||
participant "PolicyEndpointService" as PS
|
||||
participant "PolicyEndpointRegistry" as PER
|
||||
participant "PolicyEndpointDAO" as DAO
|
||||
database DB
|
||||
participant "PolicyComponent" as PC
|
||||
database AMQ
|
||||
|
||||
note over DAO,DB
|
||||
UNIQUE : policy,typeOrAspect,endpoint
|
||||
OTHER : active, modified
|
||||
SORT : modified
|
||||
end note
|
||||
|
||||
Client -> API
|
||||
API -> PS : registerPolicyEndpoint(policy, typeOrAspect, endpoint)
|
||||
PS -> DAO : createPolicyEndpoint(policy, typeOrAspect, endpoint)
|
||||
DAO -> DB : INSERT
|
||||
PS <-- DAO : success
|
||||
PS -> PER : reload(fromTime)
|
||||
PER -> DAO : getPolicyEndpoints(fromTime)
|
||||
loop each policy endpoint registration change
|
||||
PER -> PER
|
||||
end
|
||||
PS <-- PER : success
|
||||
API <-- PS : success
|
||||
Client <-- API : OK
|
||||
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,48 @@
|
||||
@startuml
|
||||
|
||||
Title: Policies: Reload Policy Endpoints (V5.2 Proposal)
|
||||
|
||||
skinparam componentStyle uml2
|
||||
|
||||
participant "Quartz"
|
||||
participant "PolicyCheckJob" as PCJ
|
||||
participant "PolicyEndpointRegistry" as PER
|
||||
participant "QueuedBehaviour" as QB
|
||||
participant "PolicyEndpointDAO" as DAO
|
||||
database DB
|
||||
participant "PolicyComponent" as PC
|
||||
database AMQ
|
||||
|
||||
== Static Route Initialization ==
|
||||
|
||||
activate PER
|
||||
PER -> QB: new
|
||||
activate PER
|
||||
PER -> PER: from("direct:policy")
|
||||
PER -> PER: dynamicRouter(method(PolicyEndpointRegistry.class, "slip"))
|
||||
deactivate PER
|
||||
deactivate PER
|
||||
|
||||
== Quartz ==
|
||||
|
||||
note over DAO,DB
|
||||
UNIQUE : policy,typeOrAspect,endpoint
|
||||
OTHER : active, modified
|
||||
SORT : modified
|
||||
end note
|
||||
|
||||
Quartz -> PCJ
|
||||
PCJ -> PER : reload(fromTime)
|
||||
PER -> DAO : getPolicyEndpoints(fromTime)
|
||||
DAO -> DB: SELECT(fromTime)
|
||||
DAO <-- DB
|
||||
PER <-- DAO
|
||||
loop each policy endpoint registration change
|
||||
PER -> PER: updateRegistrations(PolicyEndpoint)
|
||||
PER -> PC: bindClassBehaviour(policy,typeOrAspect, behaviour)
|
||||
end
|
||||
PCJ <-- PER : success
|
||||
Quartz <-- PCJ : success
|
||||
|
||||
|
||||
@enduml
|
@@ -0,0 +1,11 @@
|
||||
# Node Storage and Retrieval
|
||||
|
||||
## Properties
|
||||
|
||||
### Encrypted properties (```d:encrypted```)
|
||||
Encrypted properties are stored as BLOBs in the database, but there is no additional handling for
|
||||
them. In particular, the ```NodeService``` does not encrypt or decrypt them. It only guarantees
|
||||
that properties of this type contain objects of type ```javax.crypto.SealedObject```. It is up to
|
||||
the implementor of a custom extension to handle encryption.
|
||||
The ACS provides the helper class ```MetadataEncryptor``` which provides key handling and a one-stop-shop
|
||||
for encryption. But custom implementations do not need to use it.
|
118
repository/docs/meta-data-services/versions/README.md
Normal file
@@ -0,0 +1,118 @@
|
||||
|
||||
## Versions
|
||||
|
||||

|
||||
|
||||
### Purpose
|
||||
|
||||
***
|
||||
|
||||
### Overview
|
||||
|
||||
***
|
||||
|
||||
### Artifacts and Guidance
|
||||
|
||||
* Source Code Link:m https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/
|
||||
* License: LGPL
|
||||
* Issue Tracker Link: https://issues.alfresco.com/jira/secure/RapidBoard.jspa?projectKey=REPO&useStoredSettings=true&rapidView=379
|
||||
* Documentation Link: http://docs.alfresco.com/5.1/concepts/versioning.html
|
||||
* Contribution Model: Alfresco publishes the source code and will review proposed patch requests
|
||||
***
|
||||
|
||||
|
||||
### Prerequisite Knowledge
|
||||
|
||||
***
|
||||
|
||||
### Design
|
||||
|
||||
#### Component Model
|
||||
|
||||
#### Data Model
|
||||
|
||||
#### Data Dictionary
|
||||
|
||||
#### Flows
|
||||
|
||||
This is a series of flows illustrating when versions are created, based on changes to content and metadata.
|
||||
|
||||
##### No Autoversion on Property Updates
|
||||
Suppose the defaults in the _cm:versionable_ aspect are set as follows:
|
||||
```
|
||||
version.store.enableAutoVersioning=true
|
||||
version.store.enableAutoVersionOnUpdateProps=false
|
||||
```
|
||||
Note this is the default case when Alfresco is installed.
|
||||
|
||||

|
||||
|
||||
|
||||
##### Autoversion on Property Updates
|
||||
Suppose the defaults in the _cm:versionable_ aspect are set as follows:
|
||||
```
|
||||
version.store.enableAutoVersioning=true
|
||||
version.store.enableAutoVersionOnUpdateProps=true
|
||||
```
|
||||

|
||||
|
||||
|
||||
#### Class Diagram
|
||||
|
||||
***
|
||||
|
||||
### APIs and Interfaces
|
||||
|
||||
***
|
||||
|
||||
### Configuration
|
||||
|
||||
#### What is Versioned
|
||||
Whether an object is versionable at all is governed by the presence of the _cm:versionable_ aspect.
|
||||
If the aspect is present, the object is versioned. Otherwise it is not versioned.
|
||||
|
||||
#### Autoversioning
|
||||
|
||||
Sometimes it is desirable to create a version automatically. Whether this happens is controlled by two variables in the _cm:versionable_ aspect:
|
||||
|
||||
* cm:autoVersion
|
||||
* cm:autoVersionOnUpdateProps
|
||||
|
||||
|
||||
When _cm:autoVersion_ is true, a new version is created when the _cm:content_ of a content node changes.
|
||||
When _cm:autoVersionOnUpdateProps_ is true, a new version is created when any of the properties of a content node change.
|
||||
|
||||
The defaults for these properties are set in the contentModel.xml file in the usual way. But to simplify the admin experience, the values of these properties can also set using global properties:
|
||||
|
||||
* version.store.enableAutoVersioning
|
||||
* version.store.enableAutoVersionOnUpdateProps
|
||||
|
||||
If the values are found in the properties file they have the effect of overriding what may have been set in the contenModel.xml file.
|
||||
|
||||
The effect of these properties can be overridden by Share using a set of two properties:
|
||||
|
||||
* autoVersion
|
||||
* autoVersionProps
|
||||
|
||||
The values of these overrides are contained in the file _upload.post.config.xml_ such as in this example
|
||||
```
|
||||
<autoVersion>true</autoVersion>
|
||||
<autoVersionProps>false</autoVersionProps>
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
### Performance Considerations
|
||||
***
|
||||
|
||||
### Security Considerations
|
||||
***
|
||||
|
||||
### Cloud Considerations
|
||||
None
|
||||
|
||||
***
|
||||
|
||||
### Design Decisions
|
||||
|
||||
***
|
After Width: | Height: | Size: 94 KiB |
@@ -0,0 +1,107 @@
|
||||
@startuml
|
||||
|
||||
Title: Autoversion on Property Update - Versioning Flow
|
||||
|
||||
participant "CMIS Client" as C
|
||||
participant "Repository" as R
|
||||
participant "Version Service" as V
|
||||
participant "workspace://SpacesStore" as SS
|
||||
participant "workspace://version2Store" as VS
|
||||
participant "File System" as FS
|
||||
database "Database" as DB
|
||||
|
||||
C->R:HTTP POST
|
||||
activate R
|
||||
group "transaction"
|
||||
R->V
|
||||
activate V
|
||||
|
||||
V->SS: create node
|
||||
activate SS
|
||||
SS->DB: create node
|
||||
activate DB
|
||||
DB->SS: OK
|
||||
deactivate DB
|
||||
SS->V: OK
|
||||
deactivate SS
|
||||
note right of SS
|
||||
workspace://SpacesStore/6060b6b6-2928-4092-ab66-659a7e68c0f6
|
||||
cm:autoVersionOnUpdateProps=true
|
||||
cm:name=foo.txt
|
||||
cm:versionLabel=1.0
|
||||
end note
|
||||
V->VS: create version history node with one child node
|
||||
activate VS
|
||||
VS->DB: create nodes
|
||||
activate DB
|
||||
DB->VS: OK
|
||||
deactivate DB
|
||||
VS->V: OK
|
||||
deactivate VS
|
||||
note right of VS
|
||||
One Version History Node with one child ...
|
||||
|
||||
workspace://version2Store/62de48fa-6adc-4228-8667-df62584f98de
|
||||
cm:autoVersionOnUpdateProps=true
|
||||
cm:name=foo.txt
|
||||
cm:versionLabel=null
|
||||
ver2:versionLabel=1.0
|
||||
ver2:versionDescription=Initial Version
|
||||
|
||||
end note
|
||||
end
|
||||
V->R: OK
|
||||
deactivate V
|
||||
R->C: 200 OK
|
||||
deactivate R
|
||||
|
||||
C->R:HTTP PUT (cm:name=bar.txt)
|
||||
activate R
|
||||
group "transaction"
|
||||
R->V
|
||||
V->SS: update node
|
||||
activate V
|
||||
activate SS
|
||||
SS->DB: update node
|
||||
activate DB
|
||||
DB->SS: OK
|
||||
deactivate DB
|
||||
SS->V: OK
|
||||
deactivate SS
|
||||
note right of SS
|
||||
workspace://SpacesStore/6060b6b6-2928-4092-ab66-659a7e68c0f6
|
||||
cm:autoVersionOnUpdateProps=true
|
||||
cm:name=bar.txt
|
||||
cm:versionLabel=1.1
|
||||
end note
|
||||
|
||||
V->VS: create a new child of the version history node
|
||||
activate VS
|
||||
VS->DB: create node
|
||||
activate DB
|
||||
DB->VS: OK
|
||||
deactivate DB
|
||||
VS->V: OK
|
||||
deactivate VS
|
||||
note right of VS
|
||||
One Version History Node with two children...
|
||||
|
||||
workspace://version2Store/62de48fa-6adc-4228-8667-df62584f98de
|
||||
cm:autoVersionOnUpdateProps=true
|
||||
cm:name=foo.txt
|
||||
cm:versionLabel=null
|
||||
ver2:versionLabel=1.0
|
||||
ver2:versionDescription=Initial Version
|
||||
|
||||
NEW CHILD:
|
||||
workspace://version2Store/64d5fd85-40d3-4a44-b644-d871cb3a1030
|
||||
cm:autoVersionOnUpdateProps=true
|
||||
cm:name=bar.txt
|
||||
cm:versionLabel=1.0
|
||||
ver2:versionLabel=1.1
|
||||
ver2:versionDescription=Update Name
|
||||
|
||||
end note
|
||||
|
||||
end
|
||||
@enduml
|
After Width: | Height: | Size: 124 KiB |
@@ -0,0 +1,144 @@
|
||||
@startuml
|
||||
|
||||
Title: Autoversion on Content Update - Versioning Flow
|
||||
|
||||
participant "CMIS Client" as C
|
||||
participant "Repository" as R
|
||||
participant "Version Service" as V
|
||||
participant "workspace://SpacesStore" as SS
|
||||
participant "workspace://version2Store" as VS
|
||||
participant "File System" as FS
|
||||
database "Database" as DB
|
||||
|
||||
C->R:HTTP POST
|
||||
activate R
|
||||
group "transaction"
|
||||
R->V
|
||||
activate V
|
||||
|
||||
V->SS: create node
|
||||
activate SS
|
||||
SS->DB: create node
|
||||
activate DB
|
||||
DB->SS: OK
|
||||
deactivate DB
|
||||
SS->V: OK
|
||||
deactivate SS
|
||||
note right of SS
|
||||
workspace://SpacesStore/e8cc2b68-7482-4304-a93e-02c758a80954
|
||||
cm:autoVersionOnUpdateProps=false
|
||||
cm:name=foo.txt
|
||||
cm:versionLabel=1.0
|
||||
end note
|
||||
V->VS: create version history node with one child node
|
||||
activate VS
|
||||
VS->DB: create nodes
|
||||
activate DB
|
||||
DB->VS: OK
|
||||
deactivate DB
|
||||
VS->V: OK
|
||||
deactivate VS
|
||||
note right of VS
|
||||
One Version History Node with one child ...
|
||||
|
||||
workspace://version2Store/ce68aba3-73f6-44f9-ad9b-a9e8d77212de
|
||||
cm:autoVersionOnUpdateProps=false
|
||||
cm:name=foo.txt
|
||||
cm:versionLabel=null
|
||||
ver2:versionLabel=1.0
|
||||
ver2:versionDescription=Initial Version
|
||||
|
||||
end note
|
||||
end
|
||||
V->R: OK
|
||||
deactivate V
|
||||
R->C: 200 OK
|
||||
deactivate R
|
||||
|
||||
C->R:HTTP PUT (cm:name=bar.txt)
|
||||
activate R
|
||||
group "transaction"
|
||||
R->V
|
||||
V->SS: update node
|
||||
activate V
|
||||
activate SS
|
||||
SS->DB: update node
|
||||
activate DB
|
||||
DB->SS: OK
|
||||
deactivate DB
|
||||
SS->V: OK
|
||||
deactivate SS
|
||||
note right of SS
|
||||
workspace://SpacesStore/e8cc2b68-7482-4304-a93e-02c758a80954
|
||||
cm:autoVersionOnUpdateProps=false
|
||||
cm:name=bar.txt
|
||||
cm:versionLabel=1.0
|
||||
end note
|
||||
|
||||
note right of VS
|
||||
One Version History Node with one child...
|
||||
|
||||
workspace://version2Store/ce68aba3-73f6-44f9-ad9b-a9e8d77212de
|
||||
cm:autoVersionOnUpdateProps=false
|
||||
cm:name=foo.txt
|
||||
cm:versionLabel=null
|
||||
ver2:versionLabel=1.0
|
||||
ver2:versionDescription=Initial Version
|
||||
|
||||
end note
|
||||
|
||||
end
|
||||
|
||||
C->R:HTTP PUT (new file content)
|
||||
activate R
|
||||
group "transaction"
|
||||
R->V
|
||||
activate V
|
||||
V->SS: update node
|
||||
activate SS
|
||||
SS->FS: write file content
|
||||
activate FS
|
||||
FS->SS: OK
|
||||
deactivate FS
|
||||
SS->DB: update node to point to new file content
|
||||
activate DB
|
||||
DB->SS: OK
|
||||
deactivate DB
|
||||
SS->V: OK
|
||||
deactivate SS
|
||||
note right of SS
|
||||
workspace://SpacesStore/e8cc2b68-7482-4304-a93e-02c758a80954
|
||||
cm:autoVersionOnUpdateProps=false
|
||||
cm:name=bar.txt
|
||||
cm:versionLabel=1.0
|
||||
end note
|
||||
V->VS: create a new child of the version history node
|
||||
activate VS
|
||||
VS->DB: create node
|
||||
activate DB
|
||||
DB->VS: OK
|
||||
deactivate DB
|
||||
VS->V: OK
|
||||
deactivate VS
|
||||
note right of VS
|
||||
One Version History Node with two children...
|
||||
|
||||
workspace://version2Store/ce68aba3-73f6-44f9-ad9b-a9e8d77212de
|
||||
cm:autoVersionOnUpdateProps=false
|
||||
cm:name=foo.txt
|
||||
cm:versionLabel=null
|
||||
ver2:versionLabel=1.0
|
||||
ver2:versionDescription=Initial Version
|
||||
|
||||
NEW CHILD:
|
||||
workspace://version2Store/9fb3fb08-7cfb-4c0a-ac72-233aaf60fa1e
|
||||
cm:autoVersionOnUpdateProps=false
|
||||
cm:name=bar.txt
|
||||
cm:versionLabel=1.0
|
||||
ver2:versionLabel=1.1
|
||||
ver2:versionDescription=Update New File Content
|
||||
|
||||
end note
|
||||
end
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,37 @@
|
||||
@startuml
|
||||
|
||||
title Actions: Asynchronous Actions Classes (V5.1 Current)
|
||||
|
||||
interface ActionService {
|
||||
+ void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executeAsychronously)
|
||||
}
|
||||
class ActionServiceImpl implements ActionService {
|
||||
- ActionTransactionListener transactionListener
|
||||
- Map<String, AsynchronousActionExecutionQueue> asynchronousActionExecutionQueues
|
||||
+ void registerAsynchronousActionExecutionQueue(String key, AsynchronousActionExecutionQueue asyncExecQueue)
|
||||
+ void executeAction(...)
|
||||
- void addPostTransactionPendingAction(action, actionedUponNodeRef, checkConditions, actionChain)
|
||||
- List<PendingAction> getPostTransactionPendingActions()
|
||||
+ postCommit()
|
||||
- queueAction(PendingAction action)
|
||||
+ etc(...)
|
||||
}
|
||||
|
||||
interface AsynchronousActionExecutionQueue {
|
||||
+ void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set<String> actionChain);
|
||||
}
|
||||
class AsynchronousActionExecutionQueueImpl implements AsynchronousActionExecutionQueue {
|
||||
- ThreadPoolExecutor threadPoolExecutor
|
||||
- TransactionService transactionService
|
||||
- PolicyComponent policyComponent
|
||||
- Map<String, AbstractAsynchronousActionFilter> actionFilters
|
||||
- String id
|
||||
+ void init()
|
||||
+ void executeAction(...)
|
||||
+ etc(...)
|
||||
}
|
||||
|
||||
AsynchronousActionExecutionQueueImpl o- "1" ActionServiceImpl
|
||||
AsynchronousActionExecutionQueueImpl "0..*" -o ActionServiceImpl
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,61 @@
|
||||
@startuml
|
||||
|
||||
title Actions: Queued Actions Classes (V5.2 Proposal)
|
||||
|
||||
interface ActionService {
|
||||
+ void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executeAsychronously)
|
||||
}
|
||||
class ActionServiceImpl implements ActionService {
|
||||
- ActionTransactionListener transactionListener
|
||||
- Map<String, AsynchronousActionExecutionQueue> asynchronousActionExecutionQueues
|
||||
+ void registerAsynchronousActionExecutionQueue(String key, AsynchronousActionExecutionQueue asyncExecQueue)
|
||||
+ void executeAction(...)
|
||||
- <b>void bindPendingActionToTransaction(action, actionedUponNodeRef, checkConditions, actionChain)</b>
|
||||
- <s>void addPostTransactionPendingAction(action, actionedUponNodeRef, checkConditions, actionChain)</s>
|
||||
- List<PendingAction> getPostTransactionPendingActions()
|
||||
+ <b>beforeCommit()</b>
|
||||
+ <s>postCommit()</s>
|
||||
- queueAction(PendingAction action)
|
||||
+ etc(...)
|
||||
}
|
||||
|
||||
interface AsynchronousActionExecutionQueue {
|
||||
+ void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set<String> actionChain);
|
||||
}
|
||||
class AsynchronousActionExecutionQueueImpl implements AsynchronousActionExecutionQueue {
|
||||
- <b>ProducerTemplate producerTemplate</b>
|
||||
- <s>ThreadPoolExecutor threadPoolExecutor</s>
|
||||
- TransactionService transactionService
|
||||
- PolicyComponent policyComponent
|
||||
- Map<String, AbstractAsynchronousActionFilter> actionFilters
|
||||
- String id
|
||||
+ void init()
|
||||
+ void executeAction(...)
|
||||
+ etc(...)
|
||||
}
|
||||
|
||||
package org::apache::camel {
|
||||
class ProducerTemplate {
|
||||
+ void sendBodyAndHeaders(String endpointUri, Object body, Map<String, Object> headers)
|
||||
}
|
||||
class RouteBuilder {
|
||||
+ void configure()
|
||||
+ etc(...)
|
||||
}
|
||||
class JacksonDataFormat
|
||||
}
|
||||
|
||||
class QueuedActionProducer extends RouteBuilder {
|
||||
+ void configure()
|
||||
}
|
||||
class QueuedActionReceiver extends RouteBuilder {
|
||||
+ void configure()
|
||||
}
|
||||
|
||||
AsynchronousActionExecutionQueueImpl o- "1" ActionServiceImpl
|
||||
AsynchronousActionExecutionQueueImpl "0..*" -o ActionServiceImpl
|
||||
AsynchronousActionExecutionQueueImpl o- "1" ProducerTemplate
|
||||
QueuedActionProducer --> "uses" JacksonDataFormat
|
||||
QueuedActionReceiver --> "uses" JacksonDataFormat
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,63 @@
|
||||
@startuml
|
||||
class RuleType {
|
||||
String name
|
||||
String displayLabel
|
||||
}
|
||||
|
||||
class Rule {
|
||||
NodeRef nodeRef
|
||||
String title
|
||||
String description
|
||||
boolean ruleDisabled
|
||||
boolean executeAsynchronously
|
||||
boolean isAppliedToChildren
|
||||
}
|
||||
|
||||
class Action
|
||||
|
||||
class RuleTrigger
|
||||
|
||||
|
||||
Rule --> "1..*" RuleType
|
||||
Rule --> "1" Action
|
||||
|
||||
RuleType --> "*" RuleTrigger
|
||||
|
||||
note left of RuleType
|
||||
There are three rule types defined by default:
|
||||
- inbound
|
||||
- outbound
|
||||
- update
|
||||
end note
|
||||
|
||||
together {
|
||||
class BeforeDeleteChildAssociationRuleTrigger
|
||||
class CreateNodeRuleTrigger
|
||||
class OnCreateChildAssociationRuleTrigger
|
||||
class OnMoveNodeRuleTrigger
|
||||
class OnPropertyUpdateRuleTrigger
|
||||
class RestoreNodeRuleTrigger
|
||||
class SingleAssocRefPolicyRuleTrigger
|
||||
class SingleNodeRefPolicyRuleTrigger
|
||||
}
|
||||
|
||||
CreateNodeRuleTrigger -[hidden]--> BeforeDeleteChildAssociationRuleTrigger
|
||||
OnMoveNodeRuleTrigger -[hidden]--> OnCreateChildAssociationRuleTrigger
|
||||
OnPropertyUpdateRuleTrigger -[hidden]--> RestoreNodeRuleTrigger
|
||||
SingleAssocRefPolicyRuleTrigger -[hidden]--> SingleNodeRefPolicyRuleTrigger
|
||||
|
||||
RuleTrigger <|-- BeforeDeleteChildAssociationRuleTrigger
|
||||
RuleTrigger <|-- CreateNodeRuleTrigger
|
||||
RuleTrigger <|-- OnCreateChildAssociationRuleTrigger
|
||||
RuleTrigger <|-- OnMoveNodeRuleTrigger
|
||||
RuleTrigger <|-- OnPropertyUpdateRuleTrigger
|
||||
RuleTrigger <|-- RestoreNodeRuleTrigger
|
||||
RuleTrigger <|-- SingleAssocRefPolicyRuleTrigger
|
||||
RuleTrigger <|-- SingleNodeRefPolicyRuleTrigger
|
||||
|
||||
|
||||
note right of Action
|
||||
See separate diagram for info about Actions
|
||||
end note
|
||||
|
||||
@enduml
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,26 @@
|
||||
@startuml
|
||||
left to right direction
|
||||
skinparam linetype ortho
|
||||
|
||||
component Client
|
||||
component TransformationEngine {
|
||||
component Service
|
||||
component Router
|
||||
component Configuration
|
||||
}
|
||||
component AdminConsole
|
||||
component Log
|
||||
component TransformationFarm {
|
||||
component Worker1
|
||||
component Worker2
|
||||
component Worker3
|
||||
}
|
||||
Client->Service:calls
|
||||
Service->Router:calls
|
||||
Router->Configuration:reads
|
||||
AdminConsole->Configuration:sets
|
||||
Service->Log:writes
|
||||
AdminConsole->Log:writes
|
||||
Router->Worker1:calls
|
||||
|
||||
@enduml
|
7
repository/l10n.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
# Branch specific configuration file for localisation scripts
|
||||
|
||||
MESSAGE_SEARCH_PATH="src/main/resources/alfresco/messages/action-config*.properties src/main/resources/alfresco/messages/action-service*.properties src/main/resources/alfresco/messages/activiti-engine-messages*.properties src/main/resources/alfresco/messages/activities-service*.properties src/main/resources/alfresco/messages/activity-list*.properties src/main/resources/alfresco/messages/application-model*.properties src/main/resources/alfresco/messages/authentication*.properties src/main/resources/alfresco/messages/bootstrap-content-template-examples*.properties src/main/resources/alfresco/messages/bootstrap-example-javascripts*.properties src/main/resources/alfresco/messages/bootstrap-example-smartfoldertemplates*.properties src/main/resources/alfresco/messages/bootstrap-imapScripts*.properties src/main/resources/alfresco/messages/bootstrap-javascripts*.properties src/main/resources/alfresco/messages/bootstrap-messages*.properties src/main/resources/alfresco/messages/bootstrap-readme-template*.properties src/main/resources/alfresco/messages/bootstrap-spaces*.properties src/main/resources/alfresco/messages/bootstrap-templates*.properties src/main/resources/alfresco/messages/bootstrap-tutorial*.properties src/main/resources/alfresco/messages/bootstrap-webScripts*.properties src/main/resources/alfresco/messages/bootstrap-webScriptsExtensions*.properties src/main/resources/alfresco/messages/bpm-messages*.properties src/main/resources/alfresco/messages/categories*.properties src/main/resources/alfresco/messages/coci-service*.properties src/main/resources/alfresco/messages/content-filter-languages*.properties src/main/resources/alfresco/messages/content-model*.properties src/main/resources/alfresco/messages/copy-service*.properties src/main/resources/alfresco/messages/custommodel-service*.properties src/main/resources/alfresco/messages/discussion-messages*.properties src/main/resources/alfresco/messages/distributionpolicies-model*.properties src/main/resources/alfresco/messages/doclink-service*.properties src/main/resources/alfresco/messages/download-model*.properties src/main/resources/alfresco/messages/email-server-model*.properties src/main/resources/alfresco/messages/email-service*.properties src/main/resources/alfresco/messages/file-folder-service*.properties src/main/resources/alfresco/messages/form-service*.properties src/main/resources/alfresco/messages/forum-model*.properties src/main/resources/alfresco/messages/imap-service*.properties src/main/resources/alfresco/messages/initiate-inplace*.properties src/main/resources/alfresco/messages/invitation-service*.properties src/main/resources/alfresco/messages/lock-service*.properties src/main/resources/alfresco/messages/notification-service*.properties src/main/resources/alfresco/messages/period-provider*.properties src/main/resources/alfresco/messages/permissions-service*.properties src/main/resources/alfresco/messages/quickshare-service*.properties src/main/resources/alfresco/messages/rendition-config*.properties src/main/resources/alfresco/messages/replication*.properties src/main/resources/alfresco/messages/repoadmin-service*.properties src/main/resources/alfresco/messages/reset-password-messages*.properties src/main/resources/alfresco/messages/rule-config*.properties src/main/resources/alfresco/messages/site-model*.properties src/main/resources/alfresco/messages/site-service*.properties src/main/resources/alfresco/messages/slingshot*.properties src/main/resources/alfresco/messages/smartfolder-model*.properties src/main/resources/alfresco/messages/subscription-service*.properties src/main/resources/alfresco/messages/system-messages*.properties src/main/resources/alfresco/messages/system-model*.properties src/main/resources/alfresco/messages/template-service*.properties src/main/resources/alfresco/messages/templates-messages*.properties src/main/resources/alfresco/messages/transfer-model*.properties src/main/resources/alfresco/messages/transfer-service*.properties src/main/resources/alfresco/messages/ui-inplace*.properties src/main/resources/alfresco/messages/webdav-messages*.properties src/main/resources/alfresco/messages/workflow-package-messages*.properties src/main/resources/alfresco/workflow/invitation-moderated-workflow-messages*.properties src/main/resources/alfresco/workflow/invitation-nominated-workflow-messages*.properties src/main/resources/alfresco/workflow/workflow-messages*.properties"
|
||||
|
||||
|
||||
EXCLUDED_FILES="src/main/resources/alfresco/messages/content-service.properties src/main/resources/alfresco/messages/module-messages.properties src/main/resources/alfresco/messages/patch-service.properties src/main/resources/alfresco/messages/repoadmin-interpreter-help.properties src/main/resources/alfresco/messages/schema-update.properties src/main/resources/alfresco/messages/tenant-interpreter-help.properties src/main/resources/alfresco/messages/version-service.properties src/main/resources/alfresco/messages/workflow-interpreter-help.properties src/main/resources/alfresco/alfresco-shared.properties src/main/resources/alfresco/caches.properties src/main/resources/alfresco/repository.properties src/main/resources/alfresco/client/config/repo-clients-apps.properties src/main/resources/alfresco/domain/cache-strategies.properties src/main/resources/alfresco/domain/hibernate-cfg.properties src/main/resources/alfresco/domain/quartz.properties src/main/resources/alfresco/domain/transaction.properties src/main/resources/alfresco/keystore/keystore-passwords.properties src/main/resources/alfresco/keystore/ssl-keystore-passwords.properties src/main/resources/alfresco/keystore/ssl-truststore-passwords.properties src/main/resources/alfresco/metadata/DWGMetadataExtracter.properties src/main/resources/alfresco/metadata/HtmlMetadataExtracter.properties src/main/resources/alfresco/metadata/MailMetadataExtracter.properties src/main/resources/alfresco/metadata/MP3MetadataExtracter.properties src/main/resources/alfresco/metadata/OfficeMetadataExtracter.properties src/main/resources/alfresco/metadata/PdfBoxMetadataExtracter.properties src/main/resources/alfresco/metadata/PoiMetadataExtracter.properties src/main/resources/alfresco/metadata/RFC822MetadataExtracter.properties src/main/resources/alfresco/metadata/TikaAudioMetadataExtracter.properties src/main/resources/alfresco/metadata/TikaAutoMetadataExtracter.properties src/main/resources/alfresco/metadata/TikaSpringConfiguredMetadataExtracter.properties src/main/resources/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties src/main/resources/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication.properties src/main/resources/alfresco/subsystems/Authentication/external/external-authentication.properties src/main/resources/alfresco/subsystems/Authentication/kerberos/kerberos-authentication.properties src/main/resources/alfresco/subsystems/Authentication/ldap/ldap-authentication.properties src/main/resources/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties src/main/resources/alfresco/subsystems/email/InboundSMTP/inboundSMTP.properties src/main/resources/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties src/main/resources/alfresco/subsystems/fileServers/default/file-servers.properties src/main/resources/alfresco/subsystems/imap/default/imap-server.properties src/main/resources/alfresco/subsystems/Replication/default/replication.properties src/main/resources/alfresco/subsystems/Search/noindex/common-search.properties src/main/resources/alfresco/subsystems/Search/noindex/noindex-search.properties src/main/resources/alfresco/subsystems/Search/solr/common-search.properties src/main/resources/alfresco/subsystems/Search/solr/solr-backup.properties src/main/resources/alfresco/subsystems/Search/solr/solr-search.properties src/main/resources/alfresco/subsystems/Search/solr/facet/solr-facets-config.properties src/main/resources/alfresco/subsystems/Search/solr4/common-search.properties src/main/resources/alfresco/subsystems/Search/solr4/solr-backup.properties src/main/resources/alfresco/subsystems/Search/solr4/solr-search.properties src/main/resources/alfresco/subsystems/Search/solr6/common-search.properties src/main/resources/alfresco/subsystems/Search/solr6/solr-backup.properties src/main/resources/alfresco/subsystems/Search/solr6/solr-search.properties src/main/resources/alfresco/subsystems/Subscriptions/default/subscription-service.properties src/main/resources/alfresco/subsystems/Synchronization/default/default-synchronization.properties src/main/resources/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties src/main/resources/alfresco/subsystems/thirdparty/default/alfresco-pdf-renderer-transform.properties src/main/resources/alfresco/subsystems/thirdparty/default/imagemagick-transform.properties src/main/resources/alfresco/subsystems/Transformers/default/transformers.properties src/main/resources/org/alfresco/encryption/keystore-parameters.properties src/main/resources/org/alfresco/repo/i18n/testMessages.properties src/main/resources/org/alfresco/repo/module/tool/default-file-mapping.properties src/main/resources/alfresco/metadata/JodConverterMetadataExtracter.properties src/main/resources/alfresco/subsystems/OOoJodconverter/default/jodconverter.properties"
|
||||
|
1606
repository/pom.xml
Normal file
2
repository/src/main/META-INF/bm-remote-manifest.mf
Normal file
@@ -0,0 +1,2 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: org.alfresco.repo.model.filefolder.loader.FileFolderRemoteLoader
|
2
repository/src/main/META-INF/mmt-manifest.mf
Normal file
@@ -0,0 +1,2 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: org.alfresco.repo.module.tool.ModuleManagementTool
|
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package lib3party.org.apache.chemistry.opencmis.server.support.wrapper;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.chemistry.opencmis.commons.data.Acl;
|
||||
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
|
||||
import org.apache.chemistry.opencmis.commons.data.BulkUpdateObjectIdAndChangeToken;
|
||||
import org.apache.chemistry.opencmis.commons.data.ContentStream;
|
||||
import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
|
||||
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
|
||||
import org.apache.chemistry.opencmis.commons.data.ObjectData;
|
||||
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
|
||||
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
|
||||
import org.apache.chemistry.opencmis.commons.data.ObjectList;
|
||||
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
|
||||
import org.apache.chemistry.opencmis.commons.data.Properties;
|
||||
import org.apache.chemistry.opencmis.commons.data.RenditionData;
|
||||
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
|
||||
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
|
||||
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
|
||||
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
|
||||
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
|
||||
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
|
||||
import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
|
||||
import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
|
||||
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
|
||||
import org.apache.chemistry.opencmis.commons.server.CallContext;
|
||||
import org.apache.chemistry.opencmis.commons.server.CmisService;
|
||||
import org.apache.chemistry.opencmis.commons.server.ObjectInfo;
|
||||
import org.apache.chemistry.opencmis.commons.server.ProgressControlCmisService;
|
||||
import org.apache.chemistry.opencmis.commons.spi.Holder;
|
||||
|
||||
/**
|
||||
* An abstract CMIS service wrapper.
|
||||
* <p>
|
||||
* All service wrappers managed by {@link CmisServiceWrapperManager} must be
|
||||
* derived from this class and must provide a constructor that takes a
|
||||
* {@link CmisService} object as the sole parameter.
|
||||
*/
|
||||
public abstract class AbstractCmisServiceWrapper implements CallContextAwareCmisService, ProgressControlCmisService {
|
||||
|
||||
private CmisService service;
|
||||
private CallContext context;
|
||||
|
||||
public AbstractCmisServiceWrapper(CmisService service) {
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("Service must be set!");
|
||||
}
|
||||
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the wrapper with a set of parameters.
|
||||
*
|
||||
* @param params
|
||||
* an array of parameter objects
|
||||
*/
|
||||
public void initialize(Object[] params) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped service or the next service wrapper.
|
||||
*
|
||||
* @return the wrapped service
|
||||
*/
|
||||
public CmisService getWrappedService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the call context and propagates it down to the next service wrapper
|
||||
* or service if it implements the {@link CallContextAwareCmisService}
|
||||
* interface.
|
||||
*/
|
||||
@Override
|
||||
public void setCallContext(CallContext callContext) {
|
||||
this.context = callContext;
|
||||
|
||||
if (service instanceof CallContextAwareCmisService) {
|
||||
((CallContextAwareCmisService) service).setCallContext(callContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current call context.
|
||||
*/
|
||||
@Override
|
||||
public CallContext getCallContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
// --- processing ---
|
||||
|
||||
@Override
|
||||
public ProgressControlCmisService.Progress beforeServiceCall() {
|
||||
if (service instanceof ProgressControlCmisService) {
|
||||
return ((ProgressControlCmisService) service).beforeServiceCall();
|
||||
}
|
||||
|
||||
return ProgressControlCmisService.Progress.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressControlCmisService.Progress afterServiceCall() {
|
||||
if (service instanceof ProgressControlCmisService) {
|
||||
return ((ProgressControlCmisService) service).afterServiceCall();
|
||||
}
|
||||
|
||||
return ProgressControlCmisService.Progress.CONTINUE;
|
||||
}
|
||||
|
||||
// --- service methods ---
|
||||
|
||||
@Override
|
||||
public List<RepositoryInfo> getRepositoryInfos(ExtensionsData extension) {
|
||||
return service.getRepositoryInfos(extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension) {
|
||||
return service.getRepositoryInfo(repositoryId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDefinitionList getTypeChildren(String repositoryId, String typeId, Boolean includePropertyDefinitions,
|
||||
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
|
||||
return service
|
||||
.getTypeChildren(repositoryId, typeId, includePropertyDefinitions, maxItems, skipCount, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeDefinitionContainer> getTypeDescendants(String repositoryId, String typeId, BigInteger depth,
|
||||
Boolean includePropertyDefinitions, ExtensionsData extension) {
|
||||
return service.getTypeDescendants(repositoryId, typeId, depth, includePropertyDefinitions, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension) {
|
||||
return service.getTypeDefinition(repositoryId, typeId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDefinition createType(String repositoryId, TypeDefinition type, ExtensionsData extension) {
|
||||
return service.createType(repositoryId, type, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDefinition updateType(String repositoryId, TypeDefinition type, ExtensionsData extension) {
|
||||
return service.updateType(repositoryId, type, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteType(String repositoryId, String typeId, ExtensionsData extension) {
|
||||
service.deleteType(repositoryId, typeId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter, String orderBy,
|
||||
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
||||
Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
|
||||
return service.getChildren(repositoryId, folderId, filter, orderBy, includeAllowableActions,
|
||||
includeRelationships, renditionFilter, includePathSegment, maxItems, skipCount, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectInFolderContainer> getDescendants(String repositoryId, String folderId, BigInteger depth,
|
||||
String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
|
||||
String renditionFilter, Boolean includePathSegment, ExtensionsData extension) {
|
||||
return service.getDescendants(repositoryId, folderId, depth, filter, includeAllowableActions,
|
||||
includeRelationships, renditionFilter, includePathSegment, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectInFolderContainer> getFolderTree(String repositoryId, String folderId, BigInteger depth,
|
||||
String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
|
||||
String renditionFilter, Boolean includePathSegment, ExtensionsData extension) {
|
||||
return service.getFolderTree(repositoryId, folderId, depth, filter, includeAllowableActions,
|
||||
includeRelationships, renditionFilter, includePathSegment, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectParentData> getObjectParents(String repositoryId, String objectId, String filter,
|
||||
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
||||
Boolean includeRelativePathSegment, ExtensionsData extension) {
|
||||
return service.getObjectParents(repositoryId, objectId, filter, includeAllowableActions, includeRelationships,
|
||||
renditionFilter, includeRelativePathSegment, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension) {
|
||||
return service.getFolderParent(repositoryId, folderId, filter, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectList getCheckedOutDocs(String repositoryId, String folderId, String filter, String orderBy,
|
||||
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
||||
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
|
||||
return service.getCheckedOutDocs(repositoryId, folderId, filter, orderBy, includeAllowableActions,
|
||||
includeRelationships, renditionFilter, maxItems, skipCount, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String repositoryId, Properties properties, String folderId,
|
||||
ContentStream contentStream, VersioningState versioningState, List<String> policies, Acl addAces,
|
||||
Acl removeAces, ExtensionsData extension) {
|
||||
return service.createDocument(repositoryId, properties, folderId, contentStream, versioningState, policies,
|
||||
addAces, removeAces, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocumentFromSource(String repositoryId, String sourceId, Properties properties,
|
||||
String folderId, VersioningState versioningState, List<String> policies, Acl addAces, Acl removeAces,
|
||||
ExtensionsData extension) {
|
||||
return service.createDocumentFromSource(repositoryId, sourceId, properties, folderId, versioningState,
|
||||
policies, addAces, removeAces, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFolder(String repositoryId, Properties properties, String folderId, List<String> policies,
|
||||
Acl addAces, Acl removeAces, ExtensionsData extension) {
|
||||
return service.createFolder(repositoryId, properties, folderId, policies, addAces, removeAces, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createRelationship(String repositoryId, Properties properties, List<String> policies, Acl addAces,
|
||||
Acl removeAces, ExtensionsData extension) {
|
||||
return service.createRelationship(repositoryId, properties, policies, addAces, removeAces, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createPolicy(String repositoryId, Properties properties, String folderId, List<String> policies,
|
||||
Acl addAces, Acl removeAces, ExtensionsData extension) {
|
||||
return service.createPolicy(repositoryId, properties, folderId, policies, addAces, removeAces, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createItem(String repositoryId, Properties properties, String folderId, List<String> policies,
|
||||
Acl addAces, Acl removeAces, ExtensionsData extension) {
|
||||
return service.createItem(repositoryId, properties, folderId, policies, addAces, removeAces, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) {
|
||||
return service.getAllowableActions(repositoryId, objectId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions,
|
||||
IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
|
||||
Boolean includeAcl, ExtensionsData extension) {
|
||||
return service.getObject(repositoryId, objectId, filter, includeAllowableActions, includeRelationships,
|
||||
renditionFilter, includePolicyIds, includeAcl, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) {
|
||||
return service.getProperties(repositoryId, objectId, filter, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RenditionData> getRenditions(String repositoryId, String objectId, String renditionFilter,
|
||||
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
|
||||
return service.getRenditions(repositoryId, objectId, renditionFilter, maxItems, skipCount, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions,
|
||||
IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
|
||||
Boolean includeAcl, ExtensionsData extension) {
|
||||
return service.getObjectByPath(repositoryId, path, filter, includeAllowableActions, includeRelationships,
|
||||
renditionFilter, includePolicyIds, includeAcl, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset,
|
||||
BigInteger length, ExtensionsData extension) {
|
||||
return service.getContentStream(repositoryId, objectId, streamId, offset, length, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProperties(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
|
||||
Properties properties, ExtensionsData extension) {
|
||||
service.updateProperties(repositoryId, objectId, changeToken, properties, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BulkUpdateObjectIdAndChangeToken> bulkUpdateProperties(String repositoryId,
|
||||
List<BulkUpdateObjectIdAndChangeToken> objectIdsAndChangeTokens, Properties properties,
|
||||
List<String> addSecondaryTypeIds, List<String> removeSecondaryTypeIds, ExtensionsData extension) {
|
||||
return service.bulkUpdateProperties(repositoryId, objectIdsAndChangeTokens, properties, addSecondaryTypeIds,
|
||||
removeSecondaryTypeIds, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveObject(String repositoryId, Holder<String> objectId, String targetFolderId, String sourceFolderId,
|
||||
ExtensionsData extension) {
|
||||
service.moveObject(repositoryId, objectId, targetFolderId, sourceFolderId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteObject(String repositoryId, String objectId, Boolean allVersions, ExtensionsData extension) {
|
||||
service.deleteObject(repositoryId, objectId, allVersions, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FailedToDeleteData deleteTree(String repositoryId, String folderId, Boolean allVersions,
|
||||
UnfileObject unfileObjects, Boolean continueOnFailure, ExtensionsData extension) {
|
||||
return service.deleteTree(repositoryId, folderId, allVersions, unfileObjects, continueOnFailure, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentStream(String repositoryId, Holder<String> objectId, Boolean overwriteFlag,
|
||||
Holder<String> changeToken, ContentStream contentStream, ExtensionsData extension) {
|
||||
service.setContentStream(repositoryId, objectId, overwriteFlag, changeToken, contentStream, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteContentStream(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
|
||||
ExtensionsData extension) {
|
||||
service.deleteContentStream(repositoryId, objectId, changeToken, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendContentStream(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
|
||||
ContentStream contentStream, boolean isLastChunk, ExtensionsData extension) {
|
||||
service.appendContentStream(repositoryId, objectId, changeToken, contentStream, isLastChunk, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkOut(String repositoryId, Holder<String> objectId, ExtensionsData extension,
|
||||
Holder<Boolean> contentCopied) {
|
||||
service.checkOut(repositoryId, objectId, extension, contentCopied);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) {
|
||||
service.cancelCheckOut(repositoryId, objectId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkIn(String repositoryId, Holder<String> objectId, Boolean major, Properties properties,
|
||||
ContentStream contentStream, String checkinComment, List<String> policies, Acl addAces, Acl removeAces,
|
||||
ExtensionsData extension) {
|
||||
service.checkIn(repositoryId, objectId, major, properties, contentStream, checkinComment, policies, addAces,
|
||||
removeAces, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectData getObjectOfLatestVersion(String repositoryId, String objectId, String versionSeriesId,
|
||||
Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
|
||||
String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) {
|
||||
return service
|
||||
.getObjectOfLatestVersion(repositoryId, objectId, versionSeriesId, major, filter,
|
||||
includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl,
|
||||
extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Properties getPropertiesOfLatestVersion(String repositoryId, String objectId, String versionSeriesId,
|
||||
Boolean major, String filter, ExtensionsData extension) {
|
||||
return service.getPropertiesOfLatestVersion(repositoryId, objectId, versionSeriesId, major, filter, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectData> getAllVersions(String repositoryId, String objectId, String versionSeriesId, String filter,
|
||||
Boolean includeAllowableActions, ExtensionsData extension) {
|
||||
return service.getAllVersions(repositoryId, objectId, versionSeriesId, filter, includeAllowableActions,
|
||||
extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions,
|
||||
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
||||
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
|
||||
return service.query(repositoryId, statement, searchAllVersions, includeAllowableActions, includeRelationships,
|
||||
renditionFilter, maxItems, skipCount, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectList getContentChanges(String repositoryId, Holder<String> changeLogToken, Boolean includeProperties,
|
||||
String filter, Boolean includePolicyIds, Boolean includeAcl, BigInteger maxItems, ExtensionsData extension) {
|
||||
return service.getContentChanges(repositoryId, changeLogToken, includeProperties, filter, includePolicyIds,
|
||||
includeAcl, maxItems, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addObjectToFolder(String repositoryId, String objectId, String folderId, Boolean allVersions,
|
||||
ExtensionsData extension) {
|
||||
service.addObjectToFolder(repositoryId, objectId, folderId, allVersions, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObjectFromFolder(String repositoryId, String objectId, String folderId, ExtensionsData extension) {
|
||||
service.removeObjectFromFolder(repositoryId, objectId, folderId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectList getObjectRelationships(String repositoryId, String objectId, Boolean includeSubRelationshipTypes,
|
||||
RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions,
|
||||
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
|
||||
return service.getObjectRelationships(repositoryId, objectId, includeSubRelationshipTypes,
|
||||
relationshipDirection, typeId, filter, includeAllowableActions, maxItems, skipCount, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Acl getAcl(String repositoryId, String objectId, Boolean onlyBasicPermissions, ExtensionsData extension) {
|
||||
return service.getAcl(repositoryId, objectId, onlyBasicPermissions, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Acl applyAcl(String repositoryId, String objectId, Acl addAces, Acl removeAces,
|
||||
AclPropagation aclPropagation, ExtensionsData extension) {
|
||||
return service.applyAcl(repositoryId, objectId, addAces, removeAces, aclPropagation, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) {
|
||||
service.applyPolicy(repositoryId, policyId, objectId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) {
|
||||
service.removePolicy(repositoryId, policyId, objectId, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectData> getAppliedPolicies(String repositoryId, String objectId, String filter,
|
||||
ExtensionsData extension) {
|
||||
return service.getAppliedPolicies(repositoryId, objectId, filter, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String create(String repositoryId, Properties properties, String folderId, ContentStream contentStream,
|
||||
VersioningState versioningState, List<String> policies, ExtensionsData extension) {
|
||||
return service.create(repositoryId, properties, folderId, contentStream, versioningState, policies, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteObjectOrCancelCheckOut(String repositoryId, String objectId, Boolean allVersions,
|
||||
ExtensionsData extension) {
|
||||
service.deleteObjectOrCancelCheckOut(repositoryId, objectId, allVersions, extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Acl applyAcl(String repositoryId, String objectId, Acl aces, AclPropagation aclPropagation) {
|
||||
return service.applyAcl(repositoryId, objectId, aces, aclPropagation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInfo getObjectInfo(String repositoryId, String objectId) {
|
||||
return service.getObjectInfo(repositoryId, objectId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
service.close();
|
||||
context = null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package lib3party.org.apache.chemistry.opencmis.server.support.wrapper;
|
||||
|
||||
import org.apache.chemistry.opencmis.commons.server.CallContext;
|
||||
import org.apache.chemistry.opencmis.commons.server.CmisService;
|
||||
|
||||
/**
|
||||
* Identifies a CMIS service object that can handle call contexts.
|
||||
*/
|
||||
public interface CallContextAwareCmisService extends CmisService {
|
||||
|
||||
/**
|
||||
* Returns the current call context.
|
||||
*
|
||||
* @return the call context
|
||||
*/
|
||||
CallContext getCallContext();
|
||||
|
||||
/**
|
||||
* Sets a new call context.
|
||||
*
|
||||
* @param callContext
|
||||
* the new call context
|
||||
*/
|
||||
void setCallContext(CallContext callContext);
|
||||
}
|
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.copy.CopyBehaviourCallback;
|
||||
import org.alfresco.repo.copy.CopyDetails;
|
||||
import org.alfresco.repo.copy.CopyServicePolicies;
|
||||
import org.alfresco.repo.node.NodeServicePolicies;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeRemoveAspectPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy;
|
||||
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
|
||||
import org.alfresco.repo.policy.JavaBehaviour;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.alfresco.service.cmr.attributes.DuplicateAttributeException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Class that supports functionality of email aliasable aspect.
|
||||
*
|
||||
* @author mrogers
|
||||
* @since 2.2
|
||||
*/
|
||||
public class AliasableAspect implements NodeServicePolicies.OnAddAspectPolicy,
|
||||
NodeServicePolicies.BeforeRemoveAspectPolicy,
|
||||
NodeServicePolicies.OnUpdatePropertiesPolicy,
|
||||
NodeServicePolicies.BeforeDeleteNodePolicy,
|
||||
CopyServicePolicies.OnCopyNodePolicy
|
||||
{
|
||||
private PolicyComponent policyComponent;
|
||||
|
||||
private NodeService nodeService;
|
||||
|
||||
private AttributeService attributeService;
|
||||
|
||||
private static Log logger = LogFactory.getLog(AliasableAspect.class);
|
||||
|
||||
/**
|
||||
* The first "key" into the attribute table - identifies that the attribute is for this class
|
||||
*/
|
||||
public final static String ALIASABLE_ATTRIBUTE_KEY_1 = "AliasableAspect";
|
||||
|
||||
/**
|
||||
* The second "key" into the attribute table - identifies that the attribute is an alias
|
||||
*/
|
||||
public final static String ALIASABLE_ATTRIBUTE_KEY_2 = "Alias";
|
||||
|
||||
private final static String ERROR_MSG_DUPLICATE_ALIAS="email.server.err.duplicate_alias";
|
||||
/**
|
||||
* @param nodeService Alfresco Node Service
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param policyComponent Alfresco Policy Component
|
||||
*/
|
||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
||||
{
|
||||
this.policyComponent = policyComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring initilaise method used to register the policy behaviours
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
PropertyCheck.mandatory(this, "policyComponent", policyComponent);
|
||||
PropertyCheck.mandatory(this, "nodeService", nodeService);
|
||||
PropertyCheck.mandatory(this, "attributeService", attributeService);
|
||||
|
||||
// Register the policy behaviours
|
||||
policyComponent.bindClassBehaviour(OnAddAspectPolicy.QNAME,
|
||||
EmailServerModel.ASPECT_ALIASABLE,
|
||||
new JavaBehaviour(this, "onAddAspect", NotificationFrequency.FIRST_EVENT));
|
||||
|
||||
policyComponent.bindClassBehaviour(BeforeRemoveAspectPolicy.QNAME,
|
||||
EmailServerModel.ASPECT_ALIASABLE,
|
||||
new JavaBehaviour(this, "beforeRemoveAspect", NotificationFrequency.FIRST_EVENT));
|
||||
|
||||
policyComponent.bindClassBehaviour(OnUpdatePropertiesPolicy.QNAME,
|
||||
EmailServerModel.ASPECT_ALIASABLE,
|
||||
new JavaBehaviour(this, "onUpdateProperties"));
|
||||
|
||||
policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME,
|
||||
EmailServerModel.ASPECT_ALIASABLE,
|
||||
new JavaBehaviour(this, "beforeDeleteNode"));
|
||||
|
||||
policyComponent.bindClassBehaviour(CopyServicePolicies.OnCopyNodePolicy.QNAME,
|
||||
EmailServerModel.ASPECT_ALIASABLE,
|
||||
new JavaBehaviour(this, "getCopyCallback"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* method to normalise an email alias.
|
||||
*
|
||||
* Currently this involves trimmimg and lower casing, but it may change in future
|
||||
*
|
||||
* @param value
|
||||
* @return the normalised value.
|
||||
*/
|
||||
public static String normaliseAlias(String value)
|
||||
{
|
||||
if(value != null)
|
||||
{
|
||||
return value.toLowerCase();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email alias for the specified node.
|
||||
*
|
||||
* If the rule is broken, AlfrescoRuntimeException will be thrown.
|
||||
*
|
||||
* @param nodeRef Reference to target node
|
||||
* @param alias Alias that we want to set to the target node
|
||||
* @exception AlfrescoRuntimeException if the <b>alias</b> property is duplicated by another node.
|
||||
*/
|
||||
public void addAlias(NodeRef nodeRef, String alias)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("add email alias nodeRef:" + nodeRef + ", alias:" + alias);
|
||||
}
|
||||
// first try to see if the new alias is in use elsewhere?
|
||||
try
|
||||
{
|
||||
attributeService.createAttribute(nodeRef, ALIASABLE_ATTRIBUTE_KEY_1, ALIASABLE_ATTRIBUTE_KEY_2, normaliseAlias(alias));
|
||||
}
|
||||
catch (DuplicateAttributeException de)
|
||||
{
|
||||
throw AlfrescoRuntimeException.create(ERROR_MSG_DUPLICATE_ALIAS, normaliseAlias(alias));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the specified alias
|
||||
* @param alias to remove
|
||||
*/
|
||||
public void removeAlias(String alias)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("remove email alias alias:" + alias);
|
||||
}
|
||||
attributeService.removeAttribute(ALIASABLE_ATTRIBUTE_KEY_1, ALIASABLE_ATTRIBUTE_KEY_2, normaliseAlias(alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a node ref by its email alias
|
||||
* @return the node ref, or null if there is no node for that alias
|
||||
*/
|
||||
public NodeRef getByAlias(String alias)
|
||||
{
|
||||
Serializable value = attributeService.getAttribute(ALIASABLE_ATTRIBUTE_KEY_1, ALIASABLE_ATTRIBUTE_KEY_2, normaliseAlias(alias));
|
||||
|
||||
if(value instanceof NodeRef)
|
||||
{
|
||||
return (NodeRef)value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy#onAddAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
|
||||
* @exception AlfrescoRuntimeException Throws if the <b>alias</b> property is duplicated.
|
||||
*/
|
||||
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
Object alias = nodeService.getProperty(nodeRef, EmailServerModel.PROP_ALIAS);
|
||||
if (alias != null)
|
||||
{
|
||||
addAlias(nodeRef, alias.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map)
|
||||
* @exception AlfrescoRuntimeException Throws if the <b>alias</b> property is duplicated.
|
||||
*/
|
||||
public void onUpdateProperties(NodeRef nodeRef, Map<QName, Serializable> before, Map<QName, Serializable> after)
|
||||
{
|
||||
String oldAlias = (String)before.get(EmailServerModel.PROP_ALIAS);
|
||||
String newAlias = (String)after.get(EmailServerModel.PROP_ALIAS);
|
||||
|
||||
if(oldAlias != null && newAlias != null && (normaliseAlias(oldAlias)).equals(normaliseAlias(newAlias)))
|
||||
{
|
||||
// alias has not changed
|
||||
return;
|
||||
}
|
||||
|
||||
if (newAlias != null)
|
||||
{
|
||||
addAlias(nodeRef, newAlias);
|
||||
}
|
||||
|
||||
if(oldAlias != null)
|
||||
{
|
||||
removeAlias(oldAlias);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
String alias = (String)nodeService.getProperty(nodeRef, EmailServerModel.PROP_ALIAS);
|
||||
if(alias != null)
|
||||
{
|
||||
removeAlias(alias);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDeleteNode(NodeRef nodeRef)
|
||||
{
|
||||
String alias = (String)nodeService.getProperty(nodeRef, EmailServerModel.PROP_ALIAS);
|
||||
if(alias != null)
|
||||
{
|
||||
removeAlias(alias);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CopyBehaviourCallback getCopyCallback(QName classRef,
|
||||
CopyDetails copyDetails)
|
||||
{
|
||||
return AliasableAspectCopyBehaviourCallback.INSTANCE;
|
||||
}
|
||||
|
||||
public void setAttributeService(AttributeService attributeService)
|
||||
{
|
||||
this.attributeService = attributeService;
|
||||
}
|
||||
|
||||
public AttributeService getAttributeService()
|
||||
{
|
||||
return attributeService;
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.copy.CopyBehaviourCallback;
|
||||
import org.alfresco.repo.copy.CopyDetails;
|
||||
import org.alfresco.repo.copy.DefaultCopyBehaviourCallback;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
public class AliasableAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback
|
||||
{
|
||||
static final CopyBehaviourCallback INSTANCE = new AliasableAspectCopyBehaviourCallback();
|
||||
|
||||
/**
|
||||
* Disallows copying of the {@link EmailServerModel#ASPECT_ALIASABLE} aspect.
|
||||
*/
|
||||
@Override
|
||||
public boolean getMustCopy(QName classQName, CopyDetails copyDetails)
|
||||
{
|
||||
if (classQName.equals(EmailServerModel.ASPECT_ALIASABLE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents copying off the {@link org.alfresco.model.ContentModel#PROP_NAME <b>cm:name</b>} property.
|
||||
*/
|
||||
@Override
|
||||
public Map<QName, Serializable> getCopyProperties(
|
||||
QName classQName,
|
||||
CopyDetails copyDetails,
|
||||
Map<QName, Serializable> properties)
|
||||
{
|
||||
if (classQName.equals(EmailServerModel.ASPECT_ALIASABLE))
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.service.cmr.email.EmailMessageException;
|
||||
import org.alfresco.service.cmr.email.EmailService;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* Base implementation of an email server.
|
||||
* @since 2.2
|
||||
*/
|
||||
public abstract class EmailServer extends AbstractLifecycleBean
|
||||
{
|
||||
private static final String ERR_SENDER_BLOCKED = "email.server.err.sender_blocked";
|
||||
private static final String ERR_FROM_SYNTAX_INCORRECT = "email.server.err.from_syntax";
|
||||
|
||||
private boolean enabled;
|
||||
private String domain;
|
||||
private int port;
|
||||
private int maxConnections;
|
||||
private Set<String> blockedSenders;
|
||||
private Set<String> allowedSenders;
|
||||
private boolean hideTLS = false;
|
||||
private boolean enableTLS = true;
|
||||
private boolean requireTLS = false;
|
||||
private boolean authenticate = false;
|
||||
|
||||
private EmailService emailService;
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
private String unknownUser;
|
||||
|
||||
protected EmailServer()
|
||||
{
|
||||
this.enabled = false;
|
||||
this.port = 25;
|
||||
this.domain = null;
|
||||
this.maxConnections = 3;
|
||||
this.blockedSenders = new HashSet<String>(23);
|
||||
this.allowedSenders = new HashSet<String>(23);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param enabled Enable/disable server
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
protected String getDomain()
|
||||
{
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(String domain)
|
||||
{
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
protected int getPort()
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param port SMTP port (25 is default)
|
||||
*/
|
||||
public void setPort(int port)
|
||||
{
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of connection accepted by the server.
|
||||
* @return the maximum number of connections
|
||||
*/
|
||||
protected int getMaxConnections()
|
||||
{
|
||||
return maxConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of connection accepted by the server
|
||||
* @param maxConnections
|
||||
*/
|
||||
public void setMaxConnections(int maxConnections)
|
||||
{
|
||||
this.maxConnections = maxConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the blocked senders as a comma separated list. The entries will be trimmed of
|
||||
* all whitespace.
|
||||
*
|
||||
* @param blockedSenders a comman separated list of blocked senders
|
||||
*/
|
||||
public void setBlockedSenders(String blockedSenders)
|
||||
{
|
||||
StringTokenizer tokenizer = new StringTokenizer(blockedSenders, ",", false);
|
||||
while (tokenizer.hasMoreTokens())
|
||||
{
|
||||
String sender = tokenizer.nextToken().trim();
|
||||
this.blockedSenders.add(sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param blockedSenders a list of senders that are not allowed to email in
|
||||
*/
|
||||
public void setBlockedSendersList(List<String> blockedSenders)
|
||||
{
|
||||
this.blockedSenders.addAll(blockedSenders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the allowed senders as a comma separated list. The entries will be trimmed of
|
||||
* all whitespace.
|
||||
*
|
||||
* @param allowedSenders a comman separated list of blocked senders
|
||||
*/
|
||||
public void setAllowedSenders(String allowedSenders)
|
||||
{
|
||||
StringTokenizer tokenizer = new StringTokenizer(allowedSenders, ",", false);
|
||||
while (tokenizer.hasMoreTokens())
|
||||
{
|
||||
String sender = tokenizer.nextToken().trim();
|
||||
if (sender.length() == 0)
|
||||
{
|
||||
// Nothing
|
||||
continue;
|
||||
}
|
||||
this.allowedSenders.add(sender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param allowedSenders a list of senders that are allowed to email in
|
||||
*/
|
||||
public void setAllowedSendersList(List<String> allowedSenders)
|
||||
{
|
||||
this.allowedSenders.addAll(allowedSenders);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the service interface to interact with
|
||||
*/
|
||||
protected EmailService getEmailService()
|
||||
{
|
||||
return emailService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param emailService the service interface to interact with
|
||||
*/
|
||||
public void setEmailService(EmailService emailService)
|
||||
{
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used only for check "isNullReversePatAllowed".
|
||||
* @param unknownUser authority name
|
||||
*/
|
||||
public void setUnknownUser(String unknownUser)
|
||||
{
|
||||
this.unknownUser = unknownUser;
|
||||
}
|
||||
|
||||
protected boolean isNullReversePatAllowed()
|
||||
{
|
||||
return isAuthenticate() || (unknownUser != null && !unknownUser.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter incoming message by its sender e-mail address.
|
||||
*
|
||||
* @param sender An e-mail address of sender
|
||||
* @throws EmailMessageException if the e-mail is rejected accordingly with blocked and allowed lists
|
||||
*/
|
||||
protected void filterSender(String sender)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
if (isNullReversePatAllowed())
|
||||
{
|
||||
// allow null reverse-path: e.g.: an undeliverable mail response
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new EmailMessageException(ERR_FROM_SYNTAX_INCORRECT);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the sender is in the blocked list
|
||||
for (String blockedSender : blockedSenders)
|
||||
{
|
||||
if (sender.matches(blockedSender))
|
||||
{
|
||||
throw new EmailMessageException(ERR_SENDER_BLOCKED, sender);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any restrictions in the allowed list, then a positive match
|
||||
// is absolutely required
|
||||
if (!allowedSenders.isEmpty())
|
||||
{
|
||||
boolean matched = false;
|
||||
for (String allowedSender : allowedSenders)
|
||||
{
|
||||
if (sender.matches(allowedSender))
|
||||
{
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched)
|
||||
{
|
||||
throw new EmailMessageException(ERR_SENDER_BLOCKED, sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method is called when server is starting up.
|
||||
*/
|
||||
public abstract void startup();
|
||||
|
||||
/**
|
||||
* Method is called when server is shutting down.
|
||||
*/
|
||||
public abstract void shutdown();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent event)
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Check properties
|
||||
PropertyCheck.mandatory(this, "domain", domain);
|
||||
if (port <= 0 || port > 65535)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'port' is incorrect");
|
||||
}
|
||||
PropertyCheck.mandatory(this, "emailService", emailService);
|
||||
// Startup
|
||||
startup();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent event)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile Boolean stop = false;
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
if (args.length == 0)
|
||||
{
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
try (AbstractApplicationContext context = new ClassPathXmlApplicationContext(args))
|
||||
{
|
||||
if (!context.containsBean("emailServer"))
|
||||
{
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
public void run()
|
||||
{
|
||||
stop = true;
|
||||
synchronized (stop)
|
||||
{
|
||||
stop.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
System.out.println("Use Ctrl-C to shutdown EmailServer");
|
||||
|
||||
while (!stop)
|
||||
{
|
||||
synchronized (stop)
|
||||
{
|
||||
stop.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (BeansException e)
|
||||
{
|
||||
System.err.println("Error creating context: " + e);
|
||||
usage();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void usage()
|
||||
{
|
||||
System.err.println("Use: EmailServer configLocation1, configLocation2, ...");
|
||||
System.err.println("\t configLocation - spring xml configs with EmailServer related beans (emailServer, emailServerConfiguration, emailService)");
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticate with a user/password
|
||||
* @param userName
|
||||
* @param password
|
||||
* @return true - authenticated
|
||||
*/
|
||||
protected boolean authenticateUserNamePassword(String userName, char[] password)
|
||||
{
|
||||
try
|
||||
{
|
||||
getAuthenticationComponent().authenticate(userName, password);
|
||||
return true;
|
||||
}
|
||||
catch (AuthenticationException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide the TLS (Trusted Login Session) option
|
||||
*
|
||||
* @param hideTLS
|
||||
*/
|
||||
public void setHideTLS(boolean hideTLS)
|
||||
{
|
||||
this.hideTLS = hideTLS;
|
||||
}
|
||||
|
||||
public boolean isHideTLS()
|
||||
{
|
||||
return hideTLS;
|
||||
}
|
||||
|
||||
public void setEnableTLS(boolean enableTLS)
|
||||
{
|
||||
this.enableTLS = enableTLS;
|
||||
}
|
||||
|
||||
public boolean isEnableTLS()
|
||||
{
|
||||
return enableTLS;
|
||||
}
|
||||
|
||||
public void setRequireTLS(boolean requireTLS)
|
||||
{
|
||||
this.requireTLS = requireTLS;
|
||||
}
|
||||
|
||||
public boolean isRequireTLS()
|
||||
{
|
||||
return requireTLS;
|
||||
}
|
||||
|
||||
public void setAuthenticate(boolean enableAuthentication)
|
||||
{
|
||||
this.authenticate = enableAuthentication;
|
||||
}
|
||||
|
||||
public boolean isAuthenticate()
|
||||
{
|
||||
return authenticate;
|
||||
}
|
||||
|
||||
public void setAuthenticationComponent(AuthenticationComponent authenticationComponent)
|
||||
{
|
||||
this.authenticationComponent = authenticationComponent;
|
||||
}
|
||||
|
||||
public AuthenticationComponent getAuthenticationComponent()
|
||||
{
|
||||
return authenticationComponent;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server;
|
||||
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Class defines the costants for Email Server Model
|
||||
*
|
||||
* @see alfresco/model/emailServerModel.xml
|
||||
* @author Yan O
|
||||
* @since 2.2
|
||||
*/
|
||||
public interface EmailServerModel
|
||||
{
|
||||
// Attachable aspect
|
||||
static final QName ASPECT_ATTACHED = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "attached");
|
||||
|
||||
static final QName ASSOC_ATTACHMENT = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "attachment");
|
||||
|
||||
// Aliasable aspect
|
||||
static final QName ASPECT_ALIASABLE = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "aliasable");
|
||||
|
||||
static final QName PROP_ALIAS = QName.createQName(NamespaceService.EMAILSERVER_MODEL_URI, "alias");
|
||||
|
||||
}
|
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import org.alfresco.email.server.handler.EmailMessageHandler;
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.node.integrity.IntegrityException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.TypeDefinition;
|
||||
import org.alfresco.service.cmr.email.EmailDelivery;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailMessageException;
|
||||
import org.alfresco.service.cmr.email.EmailService;
|
||||
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.AuthorityType;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.extensions.surf.util.ParameterCheck;
|
||||
|
||||
/**
|
||||
* Concrete email service implementation. This is responsible for routing the
|
||||
* emails into the server.
|
||||
*
|
||||
* @since 2.2
|
||||
*/
|
||||
public class EmailServiceImpl implements EmailService
|
||||
{
|
||||
private static final String ERR_INBOUND_EMAIL_DISABLED = "email.server.err.inbound_mail_disabled";
|
||||
private static final String ERR_INVALID_SUBJECT = "email.server.err.invalid_subject";
|
||||
private static final String ERR_ACCESS_DENIED = "email.server.err.access_denied";
|
||||
private static final String ERR_UNKNOWN_SOURCE_ADDRESS = "email.server.err.unknown_source_address";
|
||||
private static final String ERR_USER_NOT_EMAIL_CONTRIBUTOR = "email.server.err.user_not_email_contributor";
|
||||
private static final String ERR_INVALID_NODE_ADDRESS = "email.server.err.invalid_node_address";
|
||||
private static final String ERR_HANDLER_NOT_FOUND = "email.server.err.handler_not_found";
|
||||
|
||||
private NamespaceService namespaceService;
|
||||
private NodeService nodeService;
|
||||
private SearchService searchService;
|
||||
private RetryingTransactionHelper retryingTransactionHelper;
|
||||
private AuthorityService authorityService;
|
||||
private DictionaryService dictionaryService;
|
||||
private AttributeService attributeService;
|
||||
|
||||
/**
|
||||
* The authority that needs to contain the users and groups
|
||||
* who are allowed to contribute email.
|
||||
*/
|
||||
private String emailContributorsAuthority="EMAIL_CONTRIBUTORS";
|
||||
|
||||
private static Log logger = LogFactory.getLog(EmailServiceImpl.class);
|
||||
|
||||
private boolean emailInboundEnabled;
|
||||
/** Login of user that is set as unknown. */
|
||||
private String unknownUser;
|
||||
/** List of message handlers */
|
||||
private Map<String, EmailMessageHandler> emailMessageHandlerMap;
|
||||
|
||||
public void init()
|
||||
{
|
||||
PropertyCheck.mandatory(this, "namespaceService", namespaceService);
|
||||
PropertyCheck.mandatory(this, "dictionaryService", getDictionaryService());
|
||||
PropertyCheck.mandatory(this, "searchService", searchService);
|
||||
PropertyCheck.mandatory(this, "authorityService", authorityService);
|
||||
PropertyCheck.mandatory(this, "emailMessageHandlerMap", emailMessageHandlerMap);
|
||||
PropertyCheck.mandatory(this, "attributeService", getAttributeService());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param namespaceService the service to resolve namespace prefixes
|
||||
*/
|
||||
public void setNamespaceService(NamespaceService namespaceService)
|
||||
{
|
||||
this.namespaceService = namespaceService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeService Alfresco Node Service
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchService Alfresco Search Service
|
||||
*/
|
||||
public void setSearchService(SearchService searchService)
|
||||
{
|
||||
this.searchService = searchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param retryingTransactionHelper Alfresco RetryingTransactionHelper
|
||||
*/
|
||||
public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
|
||||
{
|
||||
this.retryingTransactionHelper = retryingTransactionHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authorityService Alfresco authority service
|
||||
*/
|
||||
public void setAuthorityService(AuthorityService authorityService)
|
||||
{
|
||||
this.authorityService = authorityService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Map of message handlers
|
||||
*/
|
||||
public Map<String, EmailMessageHandler> getEmailMessageHandlerMap()
|
||||
{
|
||||
return emailMessageHandlerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param emailMessageHandlerMap Map of message handlers
|
||||
*/
|
||||
public void setEmailMessageHandlerMap(Map<String, EmailMessageHandler> emailMessageHandlerMap)
|
||||
{
|
||||
this.emailMessageHandlerMap = emailMessageHandlerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param unknownUser Login of user that should be set as unknown.
|
||||
*/
|
||||
public void setUnknownUser(String unknownUser)
|
||||
{
|
||||
this.unknownUser = unknownUser;
|
||||
}
|
||||
|
||||
public void setEmailInboundEnabled(boolean mailInboundEnabled)
|
||||
{
|
||||
this.emailInboundEnabled = mailInboundEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void importMessage(EmailDelivery delivery, EmailMessage message)
|
||||
{
|
||||
processMessage(delivery, null, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void importMessage(EmailDelivery delivery, NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
processMessage(delivery, nodeRef, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the message. Method is called after filtering by sender's address.
|
||||
* @param delivery - who gets the message and who is it from (may be different from the contents of the message)
|
||||
* @param nodeRef Addressed node (target node).
|
||||
* @param message Email message
|
||||
* @throws EmailMessageException Any exception occured inside the method will be converted and thrown as <code>EmailMessageException</code>
|
||||
*/
|
||||
private void processMessage(final EmailDelivery delivery, final NodeRef nodeRef, final EmailMessage message)
|
||||
{
|
||||
if (!emailInboundEnabled)
|
||||
{
|
||||
throw new EmailMessageException(ERR_INBOUND_EMAIL_DISABLED);
|
||||
}
|
||||
try
|
||||
{
|
||||
// Get the username for the process using the system account
|
||||
final RetryingTransactionCallback<String> getUsernameCallback = new RetryingTransactionCallback<String>()
|
||||
{
|
||||
|
||||
public String execute() throws Throwable
|
||||
{
|
||||
String userName = null;
|
||||
|
||||
userName = getUsername(delivery.getFrom());
|
||||
if(userName == null)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("unable to find user for from: " + delivery.getFrom() + ",trying message.from next");
|
||||
}
|
||||
userName = getUsername(message.getFrom());
|
||||
}
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("userName = : " + userName);
|
||||
}
|
||||
|
||||
if (userName == null)
|
||||
{
|
||||
if(unknownUser.isEmpty())
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("unable to find user for from: " + message.getFrom());
|
||||
}
|
||||
throw new EmailMessageException(ERR_UNKNOWN_SOURCE_ADDRESS, message.getFrom());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("unable to find user for from - return anonymous: ");
|
||||
}
|
||||
userName = unknownUser;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the user is part of the Email Contributors group
|
||||
if (userName == null || !isEmailContributeUser(userName))
|
||||
{
|
||||
throw new EmailMessageException(ERR_USER_NOT_EMAIL_CONTRIBUTOR, userName);
|
||||
}
|
||||
|
||||
return userName;
|
||||
}
|
||||
};
|
||||
RunAsWork<String> getUsernameRunAsWork = new RunAsWork<String>()
|
||||
{
|
||||
public String doWork() throws Exception
|
||||
{
|
||||
return retryingTransactionHelper.doInTransaction(getUsernameCallback, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
String username;
|
||||
if(delivery.getAuth() != null)
|
||||
{
|
||||
// The user has authenticated.
|
||||
username = delivery.getAuth();
|
||||
logger.debug("user has already authenticated as:" + username);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to faff with old message stuff.
|
||||
username = AuthenticationUtil.runAs(getUsernameRunAsWork, AuthenticationUtil.SYSTEM_USER_NAME);
|
||||
}
|
||||
|
||||
// Process the message using the username's account
|
||||
final RetryingTransactionCallback<Object> processMessageCallback = new RetryingTransactionCallback<Object>()
|
||||
{
|
||||
public Object execute() throws Throwable
|
||||
{
|
||||
//String recipient = message.getTo();
|
||||
String recipient = delivery.getRecipient();
|
||||
NodeRef targetNodeRef = null;
|
||||
if (nodeRef == null)
|
||||
{
|
||||
targetNodeRef = getTargetNode(recipient);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetNodeRef = nodeRef;
|
||||
}
|
||||
EmailMessageHandler messageHandler = getMessageHandler(targetNodeRef);
|
||||
try
|
||||
{
|
||||
messageHandler.processMessage(targetNodeRef, message);
|
||||
}
|
||||
catch (DuplicateChildNodeNameException e)
|
||||
{
|
||||
throw new ConcurrencyFailureException(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
RunAsWork<Object> processMessageRunAsWork = new RunAsWork<Object>()
|
||||
{
|
||||
public Object doWork() throws Exception
|
||||
{
|
||||
return retryingTransactionHelper.doInTransaction(processMessageCallback, false);
|
||||
}
|
||||
};
|
||||
AuthenticationUtil.runAs(processMessageRunAsWork, username);
|
||||
}
|
||||
catch (EmailMessageException e)
|
||||
{
|
||||
// These are email-specific errors
|
||||
throw e;
|
||||
}
|
||||
catch (AccessDeniedException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_ACCESS_DENIED, delivery.getFrom(), delivery.getRecipient());
|
||||
}
|
||||
catch (IntegrityException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_INVALID_SUBJECT);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Email message processing failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeRef Target node
|
||||
* @return Handler that can process message addressed to specific node (target node).
|
||||
* @throws EmailMessageException is thrown if a suitable message handler isn't found.
|
||||
*/
|
||||
private EmailMessageHandler getMessageHandler(NodeRef nodeRef)
|
||||
{
|
||||
ParameterCheck.mandatory("nodeRef", nodeRef);
|
||||
|
||||
QName nodeTypeQName = nodeService.getType(nodeRef);
|
||||
String prefixedNodeTypeStr = nodeTypeQName.toPrefixString(namespaceService);
|
||||
EmailMessageHandler handler = emailMessageHandlerMap.get(prefixedNodeTypeStr);
|
||||
|
||||
if( handler == null)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("did not find a handler for type:" + prefixedNodeTypeStr);
|
||||
}
|
||||
|
||||
// not a direct match on type
|
||||
// need to check the super-types (if any) of the target node
|
||||
TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName);
|
||||
while(typeDef != null)
|
||||
{
|
||||
QName parentName = typeDef.getParentName();
|
||||
if(parentName != null)
|
||||
{
|
||||
String prefixedSubTypeStr = parentName.toPrefixString(namespaceService);
|
||||
handler = emailMessageHandlerMap.get(prefixedSubTypeStr);
|
||||
if(handler != null)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("found a handler for a subtype:" + prefixedSubTypeStr);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
typeDef = dictionaryService.getType(parentName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (handler == null)
|
||||
{
|
||||
throw new EmailMessageException(ERR_HANDLER_NOT_FOUND, prefixedNodeTypeStr);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method determines target node by recipient e-mail address.
|
||||
*
|
||||
* @param recipient An e-mail address of a recipient
|
||||
* @return Reference to the target node
|
||||
* @throws EmailMessageException is thrown if the target node couldn't be determined by some reasons.
|
||||
*/
|
||||
private NodeRef getTargetNode(String recipient)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("getTarget node for" + recipient);
|
||||
}
|
||||
if (recipient == null || recipient.length() == 0)
|
||||
{
|
||||
throw new EmailMessageException(ERR_INVALID_NODE_ADDRESS, recipient);
|
||||
}
|
||||
String[] parts = recipient.split("@");
|
||||
if (parts.length != 2)
|
||||
{
|
||||
throw new EmailMessageException(ERR_INVALID_NODE_ADDRESS, recipient);
|
||||
}
|
||||
|
||||
String alias = parts[0];
|
||||
|
||||
/*
|
||||
* First lookup via the attributes service
|
||||
*
|
||||
* Then lookup by search service - may be old data prior to attributes service
|
||||
*
|
||||
* Then see if we can find a node by dbid
|
||||
*/
|
||||
|
||||
// Lookup via the attributes service
|
||||
NodeRef ref = (NodeRef)getAttributeService().getAttribute(AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_1, AliasableAspect.ALIASABLE_ATTRIBUTE_KEY_2, AliasableAspect.normaliseAlias(alias));
|
||||
|
||||
if(ref != null)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("found email alias via attribute service alias =" + alias);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
// Ok, alias wasn't found, let's try to interpret recipient address as 'node-bdid' value
|
||||
try
|
||||
{
|
||||
Long nodeId = Long.parseLong(parts[0]);
|
||||
|
||||
// Get recipient by system account
|
||||
NodeRef byNodeId = AuthenticationUtil.runAsSystem(() -> nodeService.getNodeRef(nodeId));
|
||||
|
||||
if(byNodeId != null)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("found email alias via node service =" + alias);
|
||||
}
|
||||
return byNodeId;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ne)
|
||||
{
|
||||
}
|
||||
|
||||
throw new EmailMessageException(ERR_INVALID_NODE_ADDRESS, recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate in Alfresco repository by sender's e-mail address.
|
||||
*
|
||||
* @param from Sender's email address
|
||||
* @return User name or null if the user does not exist.
|
||||
* @throws EmailMessageException Exception will be thrown if authentication is failed.
|
||||
*/
|
||||
private String getUsername(String from)
|
||||
{
|
||||
String userName = null;
|
||||
|
||||
if(from == null || from.length()==0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("getUsername from: " + from);
|
||||
}
|
||||
|
||||
|
||||
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
|
||||
String query = "TYPE:cm\\:person AND =@cm\\:email:\"" + from + "\"";
|
||||
|
||||
ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_FTS_ALFRESCO, query);
|
||||
try
|
||||
{
|
||||
if (resultSet == null || resultSet.length() == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (resultSet.length() > 1)
|
||||
{
|
||||
if(logger.isWarnEnabled())
|
||||
{
|
||||
logger.warn("found more as one result for email '" + from + "'. The first will be used");
|
||||
}
|
||||
}
|
||||
NodeRef userNode = resultSet.getNodeRef(0);
|
||||
if (nodeService.exists(userNode))
|
||||
{
|
||||
userName = DefaultTypeConverter.INSTANCE.convert(
|
||||
String.class,
|
||||
nodeService.getProperty(userNode, ContentModel.PROP_USERNAME));
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("found username: " + userName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The Lucene index returned a dead result
|
||||
throw new EmailMessageException(ERR_UNKNOWN_SOURCE_ADDRESS, from);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(resultSet != null)
|
||||
{
|
||||
resultSet.close();
|
||||
}
|
||||
}
|
||||
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the user is the member in <b>EMAIL_CONTRIBUTORS</b> group
|
||||
*
|
||||
* @param userName Alfresco user name
|
||||
* @return True if the user is member of the group
|
||||
* @exception EmailMessageException Exception will be thrown if the <b>EMAIL_CONTRIBUTORS</b> group isn't found
|
||||
*/
|
||||
private boolean isEmailContributeUser(String userName)
|
||||
{
|
||||
return this.authorityService.getAuthoritiesForUser(userName).contains(
|
||||
authorityService.getName(AuthorityType.GROUP, getEmailContributorsAuthority()));
|
||||
}
|
||||
|
||||
public void setEmailContributorsAuthority(
|
||||
String emailContributorsAuthority)
|
||||
{
|
||||
this.emailContributorsAuthority = emailContributorsAuthority;
|
||||
}
|
||||
|
||||
public String getEmailContributorsAuthority()
|
||||
{
|
||||
return emailContributorsAuthority;
|
||||
}
|
||||
|
||||
public void setDictionaryService(DictionaryService dictionaryService)
|
||||
{
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
public DictionaryService getDictionaryService()
|
||||
{
|
||||
return dictionaryService;
|
||||
}
|
||||
|
||||
public void setAttributeService(AttributeService attributeService)
|
||||
{
|
||||
this.attributeService = attributeService;
|
||||
}
|
||||
|
||||
public AttributeService getAttributeService()
|
||||
{
|
||||
return attributeService;
|
||||
}
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server;
|
||||
|
||||
import org.alfresco.email.server.impl.subetha.SubethaEmailMessage;
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.service.cmr.email.EmailDelivery;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.remoting.rmi.RmiClientInterceptor;
|
||||
|
||||
/**
|
||||
* @author Michael Shavnev
|
||||
* @since 2.2
|
||||
*/
|
||||
public class EmailServiceRemotable extends AbstractLifecycleBean implements EmailService
|
||||
{
|
||||
private String rmiRegistryHost;
|
||||
|
||||
private int rmiRegistryPort;
|
||||
|
||||
private EmailService emailServiceProxy;
|
||||
|
||||
public void setRmiRegistryHost(String rmiRegistryHost)
|
||||
{
|
||||
this.rmiRegistryHost = rmiRegistryHost;
|
||||
}
|
||||
|
||||
public void setRmiRegistryPort(int rmiRegistryPort)
|
||||
{
|
||||
this.rmiRegistryPort = rmiRegistryPort;
|
||||
}
|
||||
|
||||
public void importMessage(EmailDelivery delivery, EmailMessage message)
|
||||
{
|
||||
if (message instanceof SubethaEmailMessage)
|
||||
{
|
||||
((SubethaEmailMessage) message).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
|
||||
}
|
||||
emailServiceProxy.importMessage(delivery, message);
|
||||
}
|
||||
|
||||
public void importMessage(EmailDelivery delivery, NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
if (message instanceof SubethaEmailMessage)
|
||||
{
|
||||
((SubethaEmailMessage) message).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
|
||||
}
|
||||
emailServiceProxy.importMessage(delivery, nodeRef, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent event)
|
||||
{
|
||||
if (rmiRegistryHost == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'rmiRegistryHost' not set");
|
||||
}
|
||||
if (rmiRegistryPort == 0)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'rmiRegistryPort' not set");
|
||||
}
|
||||
|
||||
RmiClientInterceptor rmiClientInterceptor = new RmiClientInterceptor();
|
||||
rmiClientInterceptor.setRefreshStubOnConnectFailure(true);
|
||||
rmiClientInterceptor.setServiceUrl("rmi://" + rmiRegistryHost + ":" + rmiRegistryPort + "/emailService");
|
||||
emailServiceProxy = (EmailService) ProxyFactory.getProxy(EmailService.class, rmiClientInterceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent event)
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.handler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailMessagePart;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.service.cmr.repository.MimetypeService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class implements common logic for processing email messages.
|
||||
*
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public abstract class AbstractEmailMessageHandler implements EmailMessageHandler
|
||||
{
|
||||
private static final Log log = LogFactory.getLog(EmailMessageHandler.class);
|
||||
|
||||
private DictionaryService dictionaryService;
|
||||
private NodeService nodeService;
|
||||
private ContentService contentService;
|
||||
private MimetypeService mimetypeService;
|
||||
|
||||
private static Log logger = LogFactory.getLog(AbstractEmailMessageHandler.class);
|
||||
|
||||
/**
|
||||
* @return Alfresco Content Service.
|
||||
*/
|
||||
protected ContentService getContentService()
|
||||
{
|
||||
return contentService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param contentService Alfresco Content Service.
|
||||
*/
|
||||
public void setContentService(ContentService contentService)
|
||||
{
|
||||
this.contentService = contentService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Alfresco dictionary service
|
||||
*/
|
||||
protected DictionaryService getDictionaryService()
|
||||
{
|
||||
return dictionaryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dictionaryService Alfresco dictionary service
|
||||
*/
|
||||
public void setDictionaryService(DictionaryService dictionaryService)
|
||||
{
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alfresco Node Service.
|
||||
*/
|
||||
protected NodeService getNodeService()
|
||||
{
|
||||
return nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeService Alfresco Node Service.
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the service used to determine mimeypte and encoding
|
||||
*/
|
||||
protected MimetypeService getMimetypeService()
|
||||
{
|
||||
return mimetypeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mimetypeService the the service to determine mimetype and encoding
|
||||
*/
|
||||
public void setMimetypeService(MimetypeService mimetypeService)
|
||||
{
|
||||
this.mimetypeService = mimetypeService;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @param to Email address which user part specifies node-dbid
|
||||
// * @return Referance to requested node.
|
||||
// * @throws InvalidArgumentException The exception is thrown if input string has incorrect format or empty.
|
||||
// */
|
||||
// protected NodeRef getTargetNode(String to) throws InvalidArgumentException
|
||||
// {
|
||||
// if (to == null || to.length() == 0)
|
||||
// {
|
||||
// throw new InvalidArgumentException("Input string has to contain email address.");
|
||||
// }
|
||||
// String[] parts = to.split("@");
|
||||
// if (parts.length != 2)
|
||||
// {
|
||||
// throw new InvalidArgumentException("Incorrect email address format.");
|
||||
// }
|
||||
// try
|
||||
// {
|
||||
// Long dbId = Long.parseLong(parts[0]);
|
||||
// return nodeService.getNodeRef(dbId);
|
||||
// }
|
||||
// catch (NumberFormatException e)
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Write the content to the node as MIMETYPE TEXT PLAIN.
|
||||
// *
|
||||
// * @param nodeRef Target node
|
||||
// * @param content Content
|
||||
// */
|
||||
// protected void writeContent(NodeRef nodeRef, String content)
|
||||
// {
|
||||
// writeContent(nodeRef, content, MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Write the string as content to the node.
|
||||
*
|
||||
* @param nodeRef Target node.
|
||||
* @param content Text for writting.
|
||||
* @param mimetype MIME content type. For exaple you can set this parameter to "text/html" or "text/xml", etc.
|
||||
*/
|
||||
protected void writeContent(NodeRef nodeRef, String content, String mimetype)
|
||||
{
|
||||
try
|
||||
{
|
||||
InputStream inputStream = new ByteArrayInputStream(content.getBytes("UTF-8"));
|
||||
writeContent(nodeRef, inputStream, mimetype, "UTF-8");
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to write content", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write content to the node from InputStream.
|
||||
*
|
||||
* @param nodeRef Target node.
|
||||
* @param content Content stream.
|
||||
* @param mimetype MIME content type.
|
||||
* @param encoding Encoding. Can be null for text based content, n which case the best guess.
|
||||
*/
|
||||
protected void writeContent(NodeRef nodeRef, InputStream content, String mimetype, String encoding)
|
||||
{
|
||||
InputStream bis = new BufferedInputStream(content, 4092);
|
||||
|
||||
// Only guess the encoding if it has not been supplied
|
||||
if (encoding == null)
|
||||
{
|
||||
if (mimetypeService.isText(mimetype))
|
||||
{
|
||||
ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder();
|
||||
encoding = charsetFinder.getCharset(bis, mimetype).name();
|
||||
}
|
||||
else
|
||||
{
|
||||
encoding = "UTF-8";
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Write content (MimeType=\"" + mimetype + "\", Encoding=\"" + encoding + "\"");
|
||||
}
|
||||
|
||||
|
||||
ContentService contentService = getContentService();
|
||||
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
|
||||
writer.setMimetype(mimetype);
|
||||
writer.setEncoding(encoding);
|
||||
writer.putContent(bis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add emailed aspect to the specified node.
|
||||
*
|
||||
* @param nodeRef Target node.
|
||||
* @param message Mail message that will be used for extracting necessary information
|
||||
*/
|
||||
protected void addEmailedAspect(NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
|
||||
/*
|
||||
* TODO - get rid of this and use the RFC822 metadata extractor instead.
|
||||
*/
|
||||
Map<QName, Serializable> emailProps = new HashMap<QName, Serializable>();
|
||||
emailProps.put(ContentModel.PROP_SENTDATE, message.getSentDate());
|
||||
emailProps.put(ContentModel.PROP_ORIGINATOR, message.getFrom());
|
||||
emailProps.put(ContentModel.PROP_ADDRESSEE, message.getTo());
|
||||
emailProps.put(ContentModel.PROP_ADDRESSEES, (Serializable)message.getCC());
|
||||
emailProps.put(ContentModel.PROP_SUBJECT, message.getSubject());
|
||||
nodeService.addAspect(nodeRef, ContentModel.ASPECT_EMAILED, emailProps);
|
||||
|
||||
/*
|
||||
* MER
|
||||
* Can't add IMAP_CONTENT here since that means the body of the message is a mime message.
|
||||
*/
|
||||
//Map<QName, Serializable> imapProps = new HashMap<QName, Serializable>();
|
||||
//emailProps.put(ImapModel.PROP_MESSAGE_FROM, message.getFrom());
|
||||
//emailProps.put(ImapModel.PROP_MESSAGE_TO, message.getTo());
|
||||
//emailProps.put(ImapModel.PROP_MESSAGE_CC, (Serializable)message.getCC());
|
||||
//emailProps.put(ImapModel.PROP_MESSAGE_SUBJECT, message.getSubject());
|
||||
//nodeService.addAspect(nodeRef, ImapModel.ASPECT_IMAP_CONTENT, imapProps);
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Emailed aspect has been added.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new node into Alfresco repository with specified parameters. Node content isn't added.
|
||||
*
|
||||
* @param nodeService Alfresco Node Service
|
||||
* @param parent Parent node
|
||||
* @param name Name of the new node
|
||||
* @param overwrite if true then overwrite an existing node with the same name. if false the name is changed to make it unique.
|
||||
* @param assocType Association type that should be set between parent node and the new one.
|
||||
* @return Reference to created node
|
||||
*/
|
||||
protected NodeRef addContentNode(NodeService nodeService, NodeRef parent, String name, QName assocType, boolean overwrite)
|
||||
{
|
||||
String workingName = encodeSubject(name);
|
||||
|
||||
// Need to work out a new safe name.
|
||||
String baseName = FilenameUtils.getBaseName(workingName);
|
||||
String extension = FilenameUtils.getExtension(workingName);
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("addContentNode name:" + workingName);
|
||||
}
|
||||
|
||||
for(int counter = 1; counter < 10000; counter++)
|
||||
{
|
||||
QName safeQName = QName.createQNameWithValidLocalName(NamespaceService.CONTENT_MODEL_1_0_URI, workingName);
|
||||
|
||||
NodeRef childNodeRef = nodeService.getChildByName(parent, ContentModel.ASSOC_CONTAINS, workingName);
|
||||
|
||||
if (childNodeRef != null)
|
||||
{
|
||||
if(overwrite)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("overwriting existing node :" + workingName);
|
||||
}
|
||||
|
||||
// Node already exists
|
||||
// The node is present already. Make sure the name case is correct
|
||||
nodeService.setProperty(childNodeRef, ContentModel.PROP_NAME, baseName);
|
||||
return childNodeRef;
|
||||
}
|
||||
|
||||
// Node already exists and not overwrite
|
||||
String postFix = "(" + counter + ")";
|
||||
|
||||
if(baseName.length() + extension.length() + postFix.length() > QName.MAX_LENGTH )
|
||||
{
|
||||
// Need to truncate base name
|
||||
workingName = baseName.substring(0, QName.MAX_LENGTH - postFix.length() - extension.length() -1) + postFix;
|
||||
}
|
||||
else
|
||||
{
|
||||
workingName = baseName + postFix ;
|
||||
}
|
||||
if(extension.length() > 0)
|
||||
{
|
||||
workingName = workingName + "." + extension;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here if child node ref does not already exist
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("child node ref does not already exist :" + workingName);
|
||||
}
|
||||
Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>();
|
||||
contentProps.put(ContentModel.PROP_NAME, workingName);
|
||||
|
||||
ChildAssociationRef associationRef = nodeService.createNode(
|
||||
parent,
|
||||
assocType,
|
||||
safeQName,
|
||||
ContentModel.TYPE_CONTENT,
|
||||
contentProps);
|
||||
childNodeRef = associationRef.getChildRef();
|
||||
|
||||
return childNodeRef;
|
||||
}
|
||||
}
|
||||
throw new AlfrescoRuntimeException("Unable to add new file");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new node into Alfresco repository with specified parameters.
|
||||
* Node content isn't added.
|
||||
*
|
||||
* New node will be created with ContentModel.ASSOC_CONTAINS association with parent.
|
||||
*
|
||||
* @param nodeService Alfresco Node Service
|
||||
* @param parent Parent node
|
||||
* @param name Name of the new node
|
||||
* @param overwrite whether a new node should overwrite an existing node with the same name or have its name
|
||||
* mangled to be alongside the existing node.
|
||||
* @return Reference to created node
|
||||
*
|
||||
*/
|
||||
protected NodeRef addContentNode(NodeService nodeService, NodeRef parent, String name, boolean overwrite)
|
||||
{
|
||||
return addContentNode(nodeService, parent, name, ContentModel.ASSOC_CONTAINS, overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new node into Alfresco repository and mark its as an attachment.
|
||||
*
|
||||
* @param nodeService Alfresco Node Service.
|
||||
* @param folder Space/Folder to add.
|
||||
* @param mainContentNode Main content node. Any mail is added into Alfresco as one main content node and several its attachments. Each attachment related with its main node.
|
||||
* @param fileName File name for the attachment.
|
||||
* @return Reference to created node.
|
||||
*/
|
||||
protected NodeRef addAttachment(NodeService nodeService, NodeRef folder, NodeRef mainContentNode, String fileName)
|
||||
{
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Adding attachment node (name=" + fileName + ").");
|
||||
}
|
||||
|
||||
NodeRef attachmentNode = addContentNode(nodeService, folder, fileName, false);
|
||||
|
||||
// Add attached aspect
|
||||
nodeService.addAspect(mainContentNode, ContentModel.ASPECT_ATTACHABLE, null);
|
||||
// Add the association
|
||||
nodeService.createAssociation(mainContentNode, attachmentNode, ContentModel.ASSOC_ATTACHMENTS);
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Attachment has been added.");
|
||||
}
|
||||
return attachmentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return unique content name in passed folder based on provided name
|
||||
*
|
||||
* @param parent parent folder
|
||||
* @param name name of node
|
||||
* @param assocType assocType between parent and child
|
||||
* @return Original name or name in format {name}({number})
|
||||
*/
|
||||
private String getAppropriateNodeName(NodeRef parent, String name, QName assocType)
|
||||
{
|
||||
if (nodeService.getChildByName(parent, assocType, name) != null)
|
||||
{
|
||||
name = name + "(1)";
|
||||
|
||||
while (nodeService.getChildByName(parent, assocType, name) != null)
|
||||
{
|
||||
|
||||
int index = name.lastIndexOf("(");
|
||||
if ((index > 0) && name.charAt(name.length() - 1) == ')')
|
||||
{
|
||||
String posibleNumber = name.substring(index + 1, name.length() - 1);
|
||||
long num = Long.parseLong(posibleNumber) + 1;
|
||||
name = name.substring(0, index) + "(" + num + ")";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the attachments from the given message and adds them to the space. All attachments
|
||||
* are linked back to the original node that they are attached to.
|
||||
*
|
||||
* @param spaceNodeRef the space to add the documents into
|
||||
* @param nodeRef the node to which the documents will be attached
|
||||
* @param message the email message
|
||||
*/
|
||||
protected void addAttachments(NodeRef spaceNodeRef, NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
// Add attachments
|
||||
EmailMessagePart[] attachments = message.getAttachments();
|
||||
for (EmailMessagePart attachment : attachments)
|
||||
{
|
||||
String fileName = attachment.getFileName();
|
||||
|
||||
InputStream contentIs = attachment.getContent();
|
||||
|
||||
MimetypeService mimetypeService = getMimetypeService();
|
||||
String mimetype = mimetypeService.guessMimetype(fileName);
|
||||
String encoding = attachment.getEncoding();
|
||||
|
||||
NodeRef attachmentNode = addAttachment(getNodeService(), spaceNodeRef, nodeRef, fileName);
|
||||
writeContent(attachmentNode, contentIs, mimetype, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup Table for dubious characters.
|
||||
final static String[][] dubiousChars = new String[][] { { "\\", "%5c" },
|
||||
{ "/", "%2f" },
|
||||
{ "*", "%2a" },
|
||||
{ "|", "%7c" },
|
||||
{ ":", "%3a" },
|
||||
{ "\"", "%22" },
|
||||
{ "<", "%3c" },
|
||||
{ ">", "%3e" },
|
||||
{ "?", "%3f" }};
|
||||
|
||||
/**
|
||||
*
|
||||
* Subject field is used as name of the content, so we need to replace characters that are forbidden in file names.
|
||||
*
|
||||
* Trims whitespace
|
||||
*
|
||||
* Replaces characters \/*|:"<>? with their hex values.
|
||||
*
|
||||
* @param subject the string of the email subject
|
||||
*
|
||||
** @return filename
|
||||
*/
|
||||
|
||||
|
||||
public static String encodeSubject(String subject)
|
||||
{
|
||||
|
||||
// MER Removed . * , { ".", "%2e" }
|
||||
|
||||
// Filename regex from model is (.*[\"\*\\\>\<\?\/\:\|]+.*)|(.*[\.]?.*[\.]+$)|(.*[ ]+$)
|
||||
//Strip whitespace
|
||||
String result = subject.trim();
|
||||
|
||||
// replace dubious chars
|
||||
for (int i = 0; i < dubiousChars.length; i++)
|
||||
{
|
||||
result = result.replace(dubiousChars[i][0], dubiousChars[i][1]);
|
||||
}
|
||||
|
||||
// Replace trailing "." with %2e
|
||||
if(result.endsWith("."))
|
||||
{
|
||||
result = result.substring(0, result.length() -1) + "%2e";
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.handler;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.model.ApplicationModel;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.model.ForumModel;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.PropertyMap;
|
||||
|
||||
/**
|
||||
* Abstact class implements common logic for forum processing email mesages.
|
||||
*
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public abstract class AbstractForumEmailMessageHandler extends AbstractEmailMessageHandler
|
||||
{
|
||||
/**
|
||||
* Posts content
|
||||
*
|
||||
* @param nodeRef Reference to node
|
||||
* @param message Mail parser
|
||||
* @return Returns the new post node
|
||||
*/
|
||||
protected NodeRef addPostNode(NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
NodeService nodeService = getNodeService();
|
||||
Date now = new Date();
|
||||
String nodeName = "posted-" + new SimpleDateFormat("dd-MM-yyyy-hh-mm-ss").format(now) + ".html";
|
||||
|
||||
PropertyMap properties = new PropertyMap(3);
|
||||
properties.put(ContentModel.PROP_NAME, nodeName);
|
||||
|
||||
NodeRef postNodeRef = nodeService.getChildByName(nodeRef, ContentModel.ASSOC_CONTAINS, nodeName);
|
||||
if (postNodeRef == null)
|
||||
{
|
||||
ChildAssociationRef childAssoc = nodeService.createNode(
|
||||
nodeRef,
|
||||
ContentModel.ASSOC_CONTAINS,
|
||||
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nodeName),
|
||||
ForumModel.TYPE_POST,
|
||||
properties);
|
||||
postNodeRef = childAssoc.getChildRef();
|
||||
}
|
||||
|
||||
// Add necessary aspects
|
||||
properties.clear();
|
||||
properties.put(ContentModel.PROP_TITLE, nodeName);
|
||||
nodeService.addAspect(postNodeRef, ContentModel.ASPECT_TITLED, properties);
|
||||
properties.clear();
|
||||
properties.put(ApplicationModel.PROP_EDITINLINE, true);
|
||||
nodeService.addAspect(postNodeRef, ApplicationModel.ASPECT_INLINEEDITABLE, properties);
|
||||
|
||||
// Write content
|
||||
if (message.getBody() != null)
|
||||
{
|
||||
writeContent(
|
||||
postNodeRef,
|
||||
message.getBody().getContent(),
|
||||
message.getBody().getContentType(),
|
||||
message.getBody().getEncoding());
|
||||
}
|
||||
else
|
||||
{
|
||||
writeContent(postNodeRef, "<The message was empty>", MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
||||
}
|
||||
addEmailedAspect(postNodeRef, message);
|
||||
|
||||
// Done
|
||||
return postNodeRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first child with specified name
|
||||
*
|
||||
* @param nodeRef Parent node for the search
|
||||
* @param name String for search
|
||||
* @return Reference to found node or null if node isn't found
|
||||
*/
|
||||
protected NodeRef getTopicNode(NodeRef nodeRef, String name)
|
||||
{
|
||||
String workingName = encodeSubject(name);
|
||||
|
||||
NodeRef ret = getNodeService().getChildByName(nodeRef, ContentModel.ASSOC_CONTAINS, workingName);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds topic node into Alfresco repository
|
||||
*
|
||||
* @param parentNode Parent node
|
||||
* @param name Topic name
|
||||
* @return Reference to created node
|
||||
*/
|
||||
protected NodeRef addTopicNode(NodeRef parentNode, String name)
|
||||
{
|
||||
String workingName = encodeSubject(name);
|
||||
|
||||
NodeService nodeService = getNodeService();
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(1);
|
||||
properties.put(ContentModel.PROP_NAME, workingName);
|
||||
|
||||
NodeRef topicNode = nodeService.getChildByName(parentNode, ContentModel.ASSOC_CONTAINS, workingName);
|
||||
if (topicNode == null)
|
||||
{
|
||||
ChildAssociationRef association = nodeService.createNode(
|
||||
parentNode,
|
||||
ContentModel.ASSOC_CONTAINS,
|
||||
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, workingName),
|
||||
ForumModel.TYPE_TOPIC,
|
||||
properties);
|
||||
topicNode = association.getChildRef();
|
||||
}
|
||||
|
||||
// Add necessary aspects
|
||||
properties.clear();
|
||||
properties.put(ApplicationModel.PROP_ICON, "topic");
|
||||
getNodeService().addAspect(topicNode, ApplicationModel.ASPECT_UIFACETS, properties);
|
||||
|
||||
return topicNode;
|
||||
}
|
||||
}
|
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.handler;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.email.server.EmailServiceImpl;
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ApplicationModel;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.model.ForumModel;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Handler implementation address to document node.
|
||||
*
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public class DocumentEmailMessageHandler extends AbstractForumEmailMessageHandler
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(DocumentEmailMessageHandler.class);
|
||||
|
||||
private static final String forumNodeName = "EmailForum";
|
||||
|
||||
public void processMessage(NodeRef contentNodeRef, EmailMessage message)
|
||||
{
|
||||
String messageSubject = message.getSubject();
|
||||
|
||||
if (messageSubject != null && messageSubject.length() > 0)
|
||||
{
|
||||
messageSubject = message.getSubject();
|
||||
}
|
||||
else
|
||||
{
|
||||
messageSubject = "EMPTY_SUBJECT_" + System.currentTimeMillis();
|
||||
}
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("process message:" + messageSubject);
|
||||
}
|
||||
|
||||
QName nodeTypeQName = getNodeService().getType(contentNodeRef);
|
||||
|
||||
DictionaryService dictionaryService = getDictionaryService();
|
||||
if (dictionaryService.isSubClass(nodeTypeQName, ContentModel.TYPE_CONTENT))
|
||||
{
|
||||
// Find where the content resides
|
||||
NodeRef spaceNodeRef = getNodeService().getPrimaryParent(contentNodeRef).getParentRef();
|
||||
|
||||
NodeRef forumNode = getForumNode(contentNodeRef);
|
||||
|
||||
if (forumNode == null)
|
||||
{
|
||||
logger.debug("adding new forum node");
|
||||
forumNode = addForumNode(contentNodeRef);
|
||||
}
|
||||
|
||||
// Try to find existed node
|
||||
NodeRef topicNodeRef = getTopicNode(forumNode, messageSubject);
|
||||
|
||||
if (topicNodeRef == null)
|
||||
{
|
||||
logger.debug("adding new topic node");
|
||||
topicNodeRef = addTopicNode(forumNode, messageSubject);
|
||||
}
|
||||
|
||||
// Create the post
|
||||
logger.debug("add a post to the topic");
|
||||
NodeRef postNodeRef = addPostNode(topicNodeRef, message);
|
||||
|
||||
// Add attachments
|
||||
addAttachments(spaceNodeRef, postNodeRef, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AlfrescoRuntimeException("\n" +
|
||||
"Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" +
|
||||
"Check the message handler mappings.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds forum node
|
||||
*
|
||||
* @param nodeRef Paren node
|
||||
* @return Reference to created node
|
||||
*/
|
||||
private NodeRef addForumNode(NodeRef nodeRef)
|
||||
{
|
||||
NodeService nodeService = getNodeService();
|
||||
|
||||
// //Add discussable aspect to content node
|
||||
// if (!nodeService.hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE))
|
||||
// {
|
||||
// nodeService.addAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE, null);
|
||||
// }
|
||||
|
||||
//Create forum node and associate it with content node
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(1);
|
||||
properties.put(ContentModel.PROP_NAME, forumNodeName);
|
||||
ChildAssociationRef childAssoc = nodeService.createNode(nodeRef, ForumModel.ASSOC_DISCUSSION, ForumModel.ASSOC_DISCUSSION, ForumModel.TYPE_FORUM, properties);
|
||||
NodeRef forumNode = childAssoc.getChildRef();
|
||||
|
||||
//Add necessary aspects to forum node
|
||||
properties.clear();
|
||||
properties.put(ApplicationModel.PROP_ICON, "forum");
|
||||
nodeService.addAspect(forumNode, ApplicationModel.ASPECT_UIFACETS, properties);
|
||||
|
||||
return forumNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first forum node
|
||||
*
|
||||
* @param nodeRef Parent node
|
||||
* @return Found node or null
|
||||
*/
|
||||
private NodeRef getForumNode(NodeRef nodeRef)
|
||||
{
|
||||
|
||||
if (getNodeService().hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE))
|
||||
{
|
||||
List<ChildAssociationRef> assocRefList = getNodeService().getChildAssocs(nodeRef);
|
||||
Iterator<ChildAssociationRef> assocRefIter = assocRefList.iterator();
|
||||
|
||||
while (assocRefIter.hasNext())
|
||||
{
|
||||
ChildAssociationRef assocRef = assocRefIter.next();
|
||||
QName nodeTypeName = getNodeService().getType(assocRef.getChildRef());
|
||||
|
||||
if (nodeTypeName.equals(ForumModel.TYPE_FORUM))
|
||||
return assocRef.getChildRef();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.handler;
|
||||
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailMessageException;
|
||||
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
|
||||
/**
|
||||
* Interface for email handler for processing email message.
|
||||
*
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public interface EmailMessageHandler
|
||||
{
|
||||
/**
|
||||
* Method invokes for processing email message.
|
||||
*
|
||||
* @param nodeRef Target node
|
||||
* @param message Email message
|
||||
* @exception EmailMessageException Exception is thrown if processing was failed
|
||||
* @exception DuplicateChildNodeNameException Exception is thrown if node name is duplicate.
|
||||
*/
|
||||
void processMessage(NodeRef nodeRef, EmailMessage message);
|
||||
|
||||
}
|
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailMessageException;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.service.cmr.repository.MimetypeService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Handler implementation address to folder node.
|
||||
*
|
||||
* @author Yan O
|
||||
* @since 2.2
|
||||
*/
|
||||
public class FolderEmailMessageHandler extends AbstractEmailMessageHandler
|
||||
{
|
||||
private static final String MSG_RECEIVED_BY_SMTP = "email.server.msg.received_by_smtp";
|
||||
private static final String MSG_DEFAULT_SUBJECT = "email.server.msg.default_subject";
|
||||
private static final String ERR_MAIL_READ_ERROR = "email.server.err.mail_read_error";
|
||||
|
||||
private static final Log log = LogFactory.getLog(FolderEmailMessageHandler.class);
|
||||
|
||||
/**
|
||||
* overwriteDuplicates.
|
||||
* if true then overwrite an existing node with the same name. if false the name is changed to make it unique.
|
||||
*/
|
||||
private boolean overwriteDuplicates = false;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void processMessage(NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Message is processing by FolderMailMessageHandler");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Check type of the node. It must be a FOLDER
|
||||
QName nodeTypeQName = getNodeService().getType(nodeRef);
|
||||
|
||||
if (getDictionaryService().isSubClass(nodeTypeQName, ContentModel.TYPE_FOLDER))
|
||||
{
|
||||
// Add the content into the system
|
||||
addAlfrescoContent(nodeRef, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AlfrescoRuntimeException("\n" +
|
||||
"Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" +
|
||||
"Check the message handler mappings.");
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new EmailMessageException(ERR_MAIL_READ_ERROR, ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add content to Alfresco repository
|
||||
*
|
||||
* @param spaceNodeRef Addressed node
|
||||
* @param message Mail message
|
||||
* @throws IOException Exception can be thrown while saving a content into Alfresco repository.
|
||||
*/
|
||||
public void addAlfrescoContent(NodeRef spaceNodeRef, EmailMessage message) throws IOException
|
||||
{
|
||||
// Set default values for email fields
|
||||
String messageSubject = message.getSubject();
|
||||
if (messageSubject == null || messageSubject.length() == 0)
|
||||
{
|
||||
Date now = new Date();
|
||||
messageSubject = I18NUtil.getMessage(MSG_DEFAULT_SUBJECT, new SimpleDateFormat("dd-MM-yyyy-hh-mm-ss").format(now));
|
||||
}
|
||||
|
||||
String messageFrom = message.getFrom();
|
||||
if(messageFrom == null)
|
||||
{
|
||||
messageFrom = "";
|
||||
}
|
||||
|
||||
// Create main content node
|
||||
NodeRef contentNodeRef;
|
||||
contentNodeRef = addContentNode(getNodeService(), spaceNodeRef, messageSubject, overwriteDuplicates);
|
||||
// Add titled aspect
|
||||
addTitledAspect(contentNodeRef, messageSubject, messageFrom);
|
||||
// Add emailed aspect
|
||||
addEmailedAspect(contentNodeRef, message);
|
||||
// Write the message content
|
||||
if (message.getBody() != null)
|
||||
{
|
||||
if (message.getBody().getSize() == -1)
|
||||
{
|
||||
// If message body is empty we write space as a content
|
||||
// to make possible rule processing
|
||||
// (Rules don't work on empty documents)
|
||||
writeSpace(contentNodeRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
InputStream contentIs = message.getBody().getContent();
|
||||
// The message body is plain text, unless an extension has been provided
|
||||
MimetypeService mimetypeService = getMimetypeService();
|
||||
String mimetype = mimetypeService.guessMimetype(messageSubject);
|
||||
if (mimetype.equals(MimetypeMap.MIMETYPE_BINARY))
|
||||
{
|
||||
mimetype = MimetypeMap.MIMETYPE_TEXT_PLAIN;
|
||||
}
|
||||
// Use the default encoding. It will get overridden if the body is text.
|
||||
String encoding = message.getBody().getEncoding();
|
||||
|
||||
writeContent(contentNodeRef, contentIs, mimetype, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
// Add attachments
|
||||
addAttachments(spaceNodeRef, contentNodeRef, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method writes space as a content. We need this space because rules doesn't proceed documents with empty content. We need rule processing for command email messages with
|
||||
* empty body.
|
||||
*
|
||||
* @param nodeRef Reference to the parent node
|
||||
*/
|
||||
private void writeSpace(NodeRef nodeRef)
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Write space string");
|
||||
}
|
||||
|
||||
ContentService contentService = getContentService();
|
||||
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
|
||||
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
||||
writer.setEncoding("UTF-8");
|
||||
writer.putContent(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds titled aspect to the specified node.
|
||||
*
|
||||
* @param nodeRef Target node.
|
||||
* @param title Title
|
||||
*/
|
||||
private void addTitledAspect(NodeRef nodeRef, String title, String from)
|
||||
{
|
||||
Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>();
|
||||
titledProps.put(ContentModel.PROP_TITLE, title);
|
||||
titledProps.put(ContentModel.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_RECEIVED_BY_SMTP, from));
|
||||
getNodeService().addAspect(nodeRef, ContentModel.ASPECT_TITLED, titledProps);
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Titled aspect has been added.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the behaviour to be done on detecting a new message with the same subject.
|
||||
* @param overwriteDuplicates boolean
|
||||
*/
|
||||
public void setOverwriteDuplicates(boolean overwriteDuplicates)
|
||||
{
|
||||
this.overwriteDuplicates = overwriteDuplicates;
|
||||
}
|
||||
|
||||
public boolean isOverwriteDuplicates()
|
||||
{
|
||||
return overwriteDuplicates;
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.handler;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ForumModel;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Handler implementation address to forum node.
|
||||
*
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public class ForumEmailMessageHandler extends AbstractForumEmailMessageHandler
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void processMessage(NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
String messageSubject;
|
||||
|
||||
if (message.getSubject() != null)
|
||||
{
|
||||
messageSubject = message.getSubject();
|
||||
}
|
||||
else
|
||||
{
|
||||
messageSubject = "EMPTY_SUBJECT_" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
QName nodeTypeQName = getNodeService().getType(nodeRef);
|
||||
|
||||
if (getDictionaryService().isSubClass(nodeTypeQName, ForumModel.TYPE_FORUM))
|
||||
{
|
||||
NodeRef topicNode = getTopicNode(nodeRef, messageSubject);
|
||||
|
||||
if (topicNode == null)
|
||||
{
|
||||
topicNode = addTopicNode(nodeRef, messageSubject);
|
||||
}
|
||||
addPostNode(topicNode, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AlfrescoRuntimeException("\n" +
|
||||
"Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" +
|
||||
"Check the message handler mappings.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.handler;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ForumModel;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Handler implementation address to topic node.
|
||||
*
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public class TopicEmailMessageHandler extends AbstractForumEmailMessageHandler
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void processMessage(NodeRef nodeRef, EmailMessage message)
|
||||
{
|
||||
QName nodeTypeQName = getNodeService().getType(nodeRef);
|
||||
NodeRef topicNode = null;
|
||||
|
||||
if (getDictionaryService().isSubClass(nodeTypeQName, ForumModel.TYPE_TOPIC))
|
||||
{
|
||||
topicNode = nodeRef;
|
||||
}
|
||||
else if (getDictionaryService().isSubClass(nodeTypeQName, ForumModel.TYPE_POST))
|
||||
{
|
||||
topicNode = getNodeService().getPrimaryParent(nodeRef).getParentRef();
|
||||
if (topicNode == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("A POST node has no primary parent: " + nodeRef);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AlfrescoRuntimeException("\n" +
|
||||
"Message handler " + this.getClass().getName() + " cannot handle type " + nodeTypeQName + ".\n" +
|
||||
"Check the message handler mappings.");
|
||||
}
|
||||
addPostNode(topicNode, message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.impl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailMessagePart;
|
||||
|
||||
/**
|
||||
* Implementation EmailMessage interface.
|
||||
*
|
||||
* @deprecated - class not used.
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public class EmailMessageImpl implements EmailMessage
|
||||
{
|
||||
private static final long serialVersionUID = 8215537693963343756L;
|
||||
|
||||
private String to;
|
||||
private String from;
|
||||
private String subject;
|
||||
private Date sentDate;
|
||||
private EmailMessagePart body;
|
||||
|
||||
|
||||
public EmailMessageImpl(String to, String from, String subject, String body)
|
||||
{
|
||||
if (to == null)
|
||||
{
|
||||
throw new IllegalArgumentException("To cannot be null");
|
||||
}
|
||||
this.to = to;
|
||||
if (from == null)
|
||||
{
|
||||
throw new IllegalArgumentException("From cannot be null");
|
||||
}
|
||||
this.from = from;
|
||||
if (subject == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Subject cannot be null");
|
||||
}
|
||||
this.subject = subject;
|
||||
if (body == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Body cannot be null");
|
||||
}
|
||||
this.body = new EmailMessagePartImpl("Content.txt", body.getBytes());
|
||||
|
||||
this.sentDate = new Date();
|
||||
}
|
||||
|
||||
public String getTo()
|
||||
{
|
||||
return to;
|
||||
}
|
||||
|
||||
public String getFrom()
|
||||
{
|
||||
return from;
|
||||
}
|
||||
|
||||
public String getSubject()
|
||||
{
|
||||
return subject;
|
||||
}
|
||||
|
||||
public List<String> getCC()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Date getSentDate()
|
||||
{
|
||||
return sentDate;
|
||||
}
|
||||
|
||||
public EmailMessagePart getBody()
|
||||
{
|
||||
return body;
|
||||
}
|
||||
|
||||
public EmailMessagePart[] getAttachments()
|
||||
{
|
||||
return new EmailMessagePart[0];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.alfresco.service.cmr.email.EmailMessagePart;
|
||||
|
||||
/**
|
||||
* Implementation EmailMessagePart interface.
|
||||
*
|
||||
* @deprecated class not used.
|
||||
* @author maxim
|
||||
* @since 2.2
|
||||
*/
|
||||
public class EmailMessagePartImpl implements EmailMessagePart
|
||||
{
|
||||
private static final long serialVersionUID = 779186820993301580L;
|
||||
|
||||
private byte[] content;
|
||||
private String encoding;
|
||||
private String fileName;
|
||||
|
||||
|
||||
public EmailMessagePartImpl(String fileName, byte[] content)
|
||||
{
|
||||
this(fileName, null, content);
|
||||
}
|
||||
|
||||
public EmailMessagePartImpl(String fileName, String encoding, byte[] content)
|
||||
{
|
||||
if (fileName == null)
|
||||
{
|
||||
throw new IllegalArgumentException("FileName cannot be null");
|
||||
}
|
||||
this.fileName = fileName;
|
||||
|
||||
if (content == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Content cannot be null");
|
||||
}
|
||||
this.content = content;
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
this.encoding = "utf8";
|
||||
}
|
||||
else
|
||||
{
|
||||
this.encoding = encoding;
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getContent()
|
||||
{
|
||||
return new ByteArrayInputStream(content);
|
||||
}
|
||||
|
||||
public String getContentType()
|
||||
{
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
public String getEncoding()
|
||||
{
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public String getFileName()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
return content.length;
|
||||
}
|
||||
}
|
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.impl.subetha;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.BodyPart;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Multipart;
|
||||
import javax.mail.Part;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
import javax.mail.internet.MimeMessage.RecipientType;
|
||||
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailMessageException;
|
||||
import org.alfresco.service.cmr.email.EmailMessagePart;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/*
|
||||
* TODO There's a lot of metadata extraction going on in this class that
|
||||
* is duplicated by RFC822MetadataExtracter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Concrete representation of an email message as implemented for the SubEtha mail server.
|
||||
*
|
||||
* @since 2.2
|
||||
*/
|
||||
|
||||
public class SubethaEmailMessage implements EmailMessage
|
||||
{
|
||||
private static final String ERR_FAILED_TO_CREATE_MIME_MESSAGE = "email.server.err.failed_to_create_mime_message";
|
||||
private static final String ERR_EXTRACTING_FROM_ADDRESS = "email.server.err.extracting_from_address";
|
||||
private static final String ERR_NO_FROM_ADDRESS = "email.server.err.no_from_address";
|
||||
private static final String ERR_EXTRACTING_TO_ADDRESS = "email.server.err.extracting_to_address";
|
||||
private static final String ERR_NO_TO_ADDRESS = "email.server.err.no_to_address";
|
||||
private static final String ERR_EXTRACTING_SUBJECT = "email.server.err.extracting_subject";
|
||||
private static final String ERR_EXTRACTING_SENT_DATE = "email.server.err.extracting_sent_date";
|
||||
private static final String ERR_PARSE_MESSAGE = "email.server.err.parse_message";
|
||||
|
||||
private static final long serialVersionUID = -3735187524926395261L;
|
||||
|
||||
private static final Log log = LogFactory.getLog(SubethaEmailMessage.class);
|
||||
|
||||
private static final String MIME_PLAIN_TEXT = "text/plain";
|
||||
private static final String MIME_HTML_TEXT = "text/html";
|
||||
private static final String MIME_XML_TEXT = "text/xml";
|
||||
private static final String MIME_APPLICATION = "application/*";
|
||||
private static final String MIME_IMAGE = "image/*";
|
||||
private static final String MIME_MULTIPART = "multipart/*";
|
||||
private static final String MIME_RFC822 = "message/rfc822";
|
||||
private static final String FILENAME_ATTACHMENT_PREFIX = "Attachment";
|
||||
|
||||
private String from;
|
||||
private String to;
|
||||
private String subject;
|
||||
private List<String> cc;
|
||||
private Date sentDate;
|
||||
private EmailMessagePart body;
|
||||
private EmailMessagePart[] attachments;
|
||||
transient private int bodyNumber = 0;
|
||||
transient private int attachmentNumber = 0;
|
||||
transient private List<EmailMessagePart> attachmentList = new LinkedList<EmailMessagePart>();
|
||||
|
||||
protected SubethaEmailMessage()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public SubethaEmailMessage(MimeMessage mimeMessage)
|
||||
{
|
||||
processMimeMessage(mimeMessage);
|
||||
}
|
||||
|
||||
public SubethaEmailMessage(InputStream dataInputStream)
|
||||
{
|
||||
MimeMessage mimeMessage = null;
|
||||
try
|
||||
{
|
||||
mimeMessage = new MimeMessage(Session.getDefaultInstance(System.getProperties()), dataInputStream);
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_FAILED_TO_CREATE_MIME_MESSAGE, e.getMessage());
|
||||
}
|
||||
|
||||
processMimeMessage(mimeMessage);
|
||||
}
|
||||
|
||||
private void processMimeMessage(MimeMessage mimeMessage)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
Address[] addresses = null;
|
||||
try
|
||||
{
|
||||
addresses = mimeMessage.getFrom();
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_EXTRACTING_FROM_ADDRESS, e.getMessage());
|
||||
}
|
||||
if (addresses == null || addresses.length == 0)
|
||||
{
|
||||
//throw new EmailMessageException(ERR_NO_FROM_ADDRESS);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(addresses[0] instanceof InternetAddress)
|
||||
{
|
||||
from = ((InternetAddress)addresses[0]).getAddress();
|
||||
}
|
||||
else
|
||||
{
|
||||
from = addresses[0].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (to == null)
|
||||
{
|
||||
Address[] addresses = null;
|
||||
try
|
||||
{
|
||||
addresses = mimeMessage.getAllRecipients();
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_EXTRACTING_TO_ADDRESS, e.getMessage());
|
||||
}
|
||||
if (addresses == null || addresses.length == 0)
|
||||
{
|
||||
//throw new EmailMessageException(ERR_NO_TO_ADDRESS);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(addresses[0] instanceof InternetAddress)
|
||||
{
|
||||
to = ((InternetAddress)addresses[0]).getAddress();
|
||||
}
|
||||
else
|
||||
{
|
||||
to = addresses[0].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cc == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
|
||||
Address[] cca = mimeMessage.getRecipients(RecipientType.CC);
|
||||
|
||||
if(cca != null)
|
||||
{
|
||||
for(Address a : cca)
|
||||
{
|
||||
list.add(a.toString());
|
||||
}
|
||||
}
|
||||
cc = list;
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
// Do nothing - this is not a show-stopper.
|
||||
cc = null;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
subject = mimeMessage.getSubject();
|
||||
//subject = encodeSubject(mimeMessage.getSubject());
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_EXTRACTING_SUBJECT, e.getMessage());
|
||||
}
|
||||
//if (subject == null)
|
||||
//{
|
||||
// subject = ""; // Just anti-null stub :)
|
||||
//}
|
||||
|
||||
try
|
||||
{
|
||||
sentDate = mimeMessage.getSentDate();
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_EXTRACTING_SENT_DATE, e.getMessage());
|
||||
}
|
||||
if (sentDate == null)
|
||||
{
|
||||
sentDate = new Date(); // Just anti-null stub :)
|
||||
}
|
||||
|
||||
parseMessagePart(mimeMessage);
|
||||
attachments = new EmailMessagePart[attachmentList.size()];
|
||||
attachmentList.toArray(attachments);
|
||||
attachmentList = null;
|
||||
}
|
||||
|
||||
private void parseMessagePart(Part messagePart)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (messagePart.isMimeType(MIME_PLAIN_TEXT) || messagePart.isMimeType(MIME_HTML_TEXT))
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Text or HTML part was found. ContentType: " + messagePart.getContentType());
|
||||
}
|
||||
addBody(messagePart);
|
||||
}
|
||||
else if (messagePart.isMimeType(MIME_XML_TEXT))
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("XML part was found.");
|
||||
}
|
||||
addAttachment(messagePart);
|
||||
}
|
||||
else if (messagePart.isMimeType(MIME_APPLICATION))
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Application part was found.");
|
||||
}
|
||||
addAttachment(messagePart);
|
||||
}
|
||||
else if (messagePart.isMimeType(MIME_IMAGE))
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Image part was found.");
|
||||
}
|
||||
addAttachment(messagePart);
|
||||
}
|
||||
else if (messagePart.isMimeType(MIME_MULTIPART))
|
||||
{
|
||||
// if multipart, this method will be called recursively
|
||||
// for each of its parts
|
||||
Multipart mp = (Multipart) messagePart.getContent();
|
||||
int count = mp.getCount();
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("MULTIPART with " + count + " part(s) found. Processin each part...");
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
BodyPart bp = mp.getBodyPart(i);
|
||||
if (bp.getContent() instanceof MimeMultipart)
|
||||
{
|
||||
// It's multipart. Recurse.
|
||||
parseMessagePart(bp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's the body
|
||||
addBody(bp);
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("MULTIPART processed.");
|
||||
}
|
||||
|
||||
}
|
||||
else if (messagePart.isMimeType(MIME_RFC822))
|
||||
{
|
||||
// if rfc822, call this method with its content as the part
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("MIME_RFC822 part found. Processing inside part...");
|
||||
}
|
||||
|
||||
parseMessagePart((Part) messagePart.getContent());
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("MIME_RFC822 processed.");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// if all else fails, put this in the attachments map.
|
||||
// Actually we don't know what it is.
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Unrecognized part was found. Put it into attachments.");
|
||||
}
|
||||
addAttachment(messagePart);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_PARSE_MESSAGE, e.getMessage());
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_PARSE_MESSAGE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void addBody(Part messagePart) throws MessagingException
|
||||
{
|
||||
if (body != null)
|
||||
{
|
||||
attachmentList.add(new SubethaEmailMessagePart(messagePart, getPartFileName(getSubject() + " (part " + ++bodyNumber + ")", messagePart)));
|
||||
if (log.isInfoEnabled())
|
||||
{
|
||||
log.info(String.format("Attachment \"%s\" has been added.", attachmentList.get(attachmentList.size() - 1).getFileName()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
body = new SubethaEmailMessagePart(messagePart, getPartFileName(getSubject(), messagePart));
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Body has been added.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method adds a message part to the attachments list
|
||||
*
|
||||
* @param messagePart A part of message
|
||||
* @throws EmailMessageException
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private void addAttachment(Part messagePart) throws MessagingException
|
||||
{
|
||||
String fileName = getPartFileName(FILENAME_ATTACHMENT_PREFIX + attachmentNumber, messagePart);
|
||||
attachmentList.add(new SubethaEmailMessagePart(messagePart, fileName));
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Attachment added: " + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method extracts file name from a message part for saving its as aa attachment. If the file name can't be extracted, it will be generated based on defaultPrefix parameter.
|
||||
*
|
||||
* @param defaultPrefix This prefix fill be used for generating file name.
|
||||
* @param messagePart A part of message
|
||||
* @return File name.
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private String getPartFileName(String defaultPrefix, Part messagePart) throws MessagingException
|
||||
{
|
||||
String fileName = messagePart.getFileName();
|
||||
if (fileName != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
fileName = MimeUtility.decodeText(fileName);
|
||||
}
|
||||
catch (UnsupportedEncodingException ex)
|
||||
{
|
||||
// Nothing to do :)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName = defaultPrefix;
|
||||
if (messagePart.isMimeType(MIME_PLAIN_TEXT))
|
||||
fileName += ".txt";
|
||||
else if (messagePart.isMimeType(MIME_HTML_TEXT))
|
||||
fileName += ".html";
|
||||
else if (messagePart.isMimeType(MIME_XML_TEXT))
|
||||
fileName += ".xml";
|
||||
else if (messagePart.isMimeType(MIME_IMAGE))
|
||||
fileName += ".gif";
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setRmiRegistry(String rmiRegistryHost, int rmiRegistryPort)
|
||||
{
|
||||
if (body instanceof SubethaEmailMessagePart)
|
||||
{
|
||||
((SubethaEmailMessagePart) body).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
|
||||
}
|
||||
|
||||
for (EmailMessagePart attachment : attachments)
|
||||
{
|
||||
if (attachment instanceof SubethaEmailMessagePart)
|
||||
{
|
||||
((SubethaEmailMessagePart) attachment).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getCC()
|
||||
{
|
||||
return cc;
|
||||
}
|
||||
|
||||
|
||||
public String getFrom()
|
||||
{
|
||||
return from;
|
||||
}
|
||||
|
||||
public String getTo()
|
||||
{
|
||||
return to;
|
||||
}
|
||||
|
||||
public Date getSentDate()
|
||||
{
|
||||
return sentDate;
|
||||
}
|
||||
|
||||
public String getSubject()
|
||||
{
|
||||
return subject;
|
||||
}
|
||||
|
||||
public EmailMessagePart getBody()
|
||||
{
|
||||
return body;
|
||||
}
|
||||
|
||||
public EmailMessagePart[] getAttachments()
|
||||
{
|
||||
return attachments;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.impl.subetha;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Part;
|
||||
|
||||
import org.alfresco.service.cmr.email.EmailMessageException;
|
||||
import org.alfresco.service.cmr.email.EmailMessagePart;
|
||||
import org.springframework.extensions.surf.util.ParameterCheck;
|
||||
import org.alfresco.util.remote.RemotableInputStream;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* @since 2.2
|
||||
*/
|
||||
public class SubethaEmailMessagePart implements EmailMessagePart
|
||||
{
|
||||
private static final String ERR_UNSUPPORTED_ENCODING = "email.server.err.usupported_encoding";
|
||||
private static final String ERR_FAILED_TO_READ_CONTENT_STREAM = "email.server.err.failed_to_read_content_stream";
|
||||
private static final String ERR_INCORRECT_MESSAGE_PART = "email.server.err.incorrect_message_part";
|
||||
|
||||
private static final long serialVersionUID = -8530238872199733096L;
|
||||
|
||||
static final Log log = LogFactory.getLog(SubethaEmailMessagePart.class);
|
||||
|
||||
private static final Pattern encodingExtractor = Pattern.compile("charset\\s*=[\\s\"]*([^\";\\s]*)");
|
||||
|
||||
private String encoding;
|
||||
private String fileName;
|
||||
private int fileSize = -1;
|
||||
private String contentType;
|
||||
private InputStream contentInputStream;
|
||||
|
||||
private String rmiRegistryHost;
|
||||
private int rmiRegistryPort;
|
||||
|
||||
protected SubethaEmailMessagePart()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Object can be built on existing message part only.
|
||||
*
|
||||
* @param messagePart Message part.
|
||||
*/
|
||||
public SubethaEmailMessagePart(Part messagePart)
|
||||
{
|
||||
ParameterCheck.mandatory("messagePart", messagePart);
|
||||
|
||||
try
|
||||
{
|
||||
fileSize = messagePart.getSize();
|
||||
fileName = messagePart.getFileName();
|
||||
contentType = messagePart.getContentType();
|
||||
|
||||
Matcher matcher = encodingExtractor.matcher(contentType);
|
||||
if (matcher.find())
|
||||
{
|
||||
encoding = matcher.group(1);
|
||||
if (!Charset.isSupported(encoding))
|
||||
{
|
||||
throw new EmailMessageException(ERR_UNSUPPORTED_ENCODING, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
contentInputStream = messagePart.getInputStream();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new EmailMessageException(ERR_FAILED_TO_READ_CONTENT_STREAM, ex.getMessage());
|
||||
}
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
throw new EmailMessageException(ERR_INCORRECT_MESSAGE_PART, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public SubethaEmailMessagePart(Part messagePart, String fileName)
|
||||
{
|
||||
this(messagePart);
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
|
||||
public InputStream getContent()
|
||||
{
|
||||
return contentInputStream;
|
||||
}
|
||||
|
||||
public String getContentType()
|
||||
{
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public String getEncoding()
|
||||
{
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public String getFileName()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
|
||||
public void setRmiRegistry(String rmiRegistryHost, int rmiRegistryPort)
|
||||
{
|
||||
this.rmiRegistryHost = rmiRegistryHost;
|
||||
this.rmiRegistryPort = rmiRegistryPort;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException
|
||||
{
|
||||
contentInputStream = new RemotableInputStream(rmiRegistryHost, rmiRegistryPort, contentInputStream);
|
||||
out.defaultWriteObject();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
|
||||
{
|
||||
in.defaultReadObject();
|
||||
}
|
||||
}
|
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.email.server.impl.subetha;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import org.alfresco.email.server.EmailServer;
|
||||
import org.alfresco.service.cmr.email.EmailDelivery;
|
||||
import org.alfresco.service.cmr.email.EmailMessage;
|
||||
import org.alfresco.service.cmr.email.EmailMessageException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.subethamail.smtp.AuthenticationHandler;
|
||||
import org.subethamail.smtp.AuthenticationHandlerFactory;
|
||||
import org.subethamail.smtp.MessageContext;
|
||||
import org.subethamail.smtp.MessageHandler;
|
||||
import org.subethamail.smtp.MessageHandlerFactory;
|
||||
import org.subethamail.smtp.RejectException;
|
||||
import org.subethamail.smtp.TooMuchDataException;
|
||||
import org.subethamail.smtp.auth.EasyAuthenticationHandlerFactory;
|
||||
import org.subethamail.smtp.auth.LoginFailedException;
|
||||
import org.subethamail.smtp.auth.UsernamePasswordValidator;
|
||||
import org.subethamail.smtp.io.DeferredFileOutputStream;
|
||||
import org.subethamail.smtp.server.SMTPServer;
|
||||
|
||||
/**
|
||||
* @since 2.2
|
||||
*/
|
||||
public class SubethaEmailServer extends EmailServer
|
||||
{
|
||||
private final static Log logger = LogFactory.getLog(SubethaEmailServer.class);
|
||||
|
||||
private SMTPServer serverImpl;
|
||||
|
||||
protected SubethaEmailServer()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup()
|
||||
{
|
||||
serverImpl = new SMTPServer(new HandlerFactory());
|
||||
|
||||
// MER - May need to override SMTPServer.createSSLSocket to specify non default keystore.
|
||||
serverImpl.setPort(getPort());
|
||||
serverImpl.setHostName(getDomain());
|
||||
serverImpl.setMaxConnections(getMaxConnections());
|
||||
|
||||
serverImpl.setHideTLS(isHideTLS());
|
||||
serverImpl.setEnableTLS(isEnableTLS());
|
||||
serverImpl.setRequireTLS(isRequireTLS());
|
||||
|
||||
if(isAuthenticate())
|
||||
{
|
||||
AuthenticationHandlerFactory authenticationHandler = new EasyAuthenticationHandlerFactory(new AlfrescoLoginUsernamePasswordValidator());
|
||||
serverImpl.setAuthenticationHandlerFactory(authenticationHandler);
|
||||
}
|
||||
|
||||
serverImpl.start();
|
||||
log.info("Inbound SMTP Email Server has started successfully, on hostName:" + getDomain() + "port:" + getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
serverImpl.stop();
|
||||
log.info("Inbound SMTP Email Server has stopped successfully");
|
||||
}
|
||||
|
||||
class AlfrescoLoginUsernamePasswordValidator implements UsernamePasswordValidator
|
||||
{
|
||||
@Override
|
||||
public void login(String username, String password)
|
||||
throws LoginFailedException
|
||||
{
|
||||
if(!authenticateUserNamePassword(username, password.toCharArray()))
|
||||
{
|
||||
throw new LoginFailedException("unable to log on");
|
||||
}
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("User authenticated successfully" + username);
|
||||
}
|
||||
// here if authentication successful.
|
||||
}
|
||||
}
|
||||
|
||||
class HandlerFactory implements MessageHandlerFactory
|
||||
{
|
||||
public MessageHandler create(MessageContext messageContext)
|
||||
{
|
||||
return new Handler(messageContext);
|
||||
}
|
||||
};
|
||||
|
||||
class Handler implements MessageHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* 7 megs by default. The server will buffer incoming messages to disk when they hit this limit in the DATA received.
|
||||
*/
|
||||
private int DEFAULT_DATA_DEFERRED_SIZE = 1024 * 1024 * 7;
|
||||
|
||||
private List<String> EMPTY_LIST = new LinkedList<String>();
|
||||
|
||||
private MessageContext messageContext;
|
||||
|
||||
private String from;
|
||||
|
||||
List<EmailDelivery> deliveries = new ArrayList<EmailDelivery>();
|
||||
|
||||
public Handler(MessageContext messageContext)
|
||||
{
|
||||
this.messageContext = messageContext;
|
||||
}
|
||||
|
||||
public MessageContext getMessageContext()
|
||||
{
|
||||
return messageContext;
|
||||
}
|
||||
|
||||
|
||||
public void from(String fromString) throws RejectException
|
||||
{
|
||||
try
|
||||
{
|
||||
InternetAddress a = new InternetAddress(fromString);
|
||||
from = a.getAddress();
|
||||
}
|
||||
catch (AddressException e)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("check whether user is allowed to send email from" + from);
|
||||
}
|
||||
filterSender(from);
|
||||
}
|
||||
catch (EmailMessageException e)
|
||||
{
|
||||
throw new RejectException(554, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void recipient(String recipient) throws RejectException
|
||||
{
|
||||
AuthenticationHandler auth = messageContext.getAuthenticationHandler();
|
||||
|
||||
deliveries.add(new EmailDelivery(recipient, from, auth != null ? (String)auth.getIdentity(): null));
|
||||
}
|
||||
|
||||
public void data(InputStream data) throws TooMuchDataException, IOException, RejectException
|
||||
{
|
||||
if (deliveries.size() == 1)
|
||||
{
|
||||
EmailDelivery delivery = deliveries.get(0);
|
||||
processDelivery(delivery, data);
|
||||
}
|
||||
else if (deliveries.size() > 1)
|
||||
{
|
||||
DeferredFileOutputStream dfos = null;
|
||||
try
|
||||
{
|
||||
dfos = new DeferredFileOutputStream(DEFAULT_DATA_DEFERRED_SIZE);
|
||||
byte[] bytes = new byte[1024 * 8];
|
||||
int bytesRead;
|
||||
while ((bytesRead = data.read(bytes)) != -1)
|
||||
{
|
||||
dfos.write(bytes, 0, bytesRead);
|
||||
}
|
||||
|
||||
for (EmailDelivery delivery : deliveries)
|
||||
{
|
||||
processDelivery(delivery, dfos.getInputStream());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
dfos.close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processDelivery(EmailDelivery delivery, InputStream data) throws RejectException
|
||||
{
|
||||
EmailMessage emailMessage;
|
||||
try
|
||||
{
|
||||
emailMessage = new SubethaEmailMessage(data);
|
||||
getEmailService().importMessage(delivery, emailMessage);
|
||||
}
|
||||
catch (EmailMessageException e)
|
||||
{
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("about to raise EmailMessageException", e);
|
||||
}
|
||||
throw new RejectException(554, e.getMessage());
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
log.error(e.getMessage(), e);
|
||||
throw new RejectException(554, "An internal error prevented mail delivery.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done()
|
||||
{
|
||||
deliveries.clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.encryption;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @since 4.0
|
||||
*
|
||||
*/
|
||||
public class BootstrapReEncryptor extends AbstractLifecycleBean
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(BootstrapReEncryptor.class);
|
||||
|
||||
private boolean enabled;
|
||||
private ReEncryptor reEncryptor;
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setReEncryptor(ReEncryptor reEncryptor)
|
||||
{
|
||||
this.reEncryptor = reEncryptor;
|
||||
}
|
||||
|
||||
public int reEncrypt()
|
||||
{
|
||||
try
|
||||
{
|
||||
return reEncryptor.bootstrapReEncrypt();
|
||||
}
|
||||
catch(MissingKeyException e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Bootstrap re-encryption failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent event)
|
||||
{
|
||||
if(enabled)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Re-encrypting encryptable properties...");
|
||||
}
|
||||
int propertiesReEncrypted = reEncrypt();
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("...done, re-encrypted " + propertiesReEncrypted + " properties.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent event)
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.encryption;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
|
||||
/**
|
||||
* The EncryptionChecker checks the state of the repository's encryption system.
|
||||
* In particular it checks:
|
||||
* <ul>
|
||||
* <li> that the keystore exists and, if not, creates one.
|
||||
* <li> that the encryption keys have not been changed. If so, the bootstrap will be halted.
|
||||
* </ul>
|
||||
*
|
||||
* @since 4.0
|
||||
*
|
||||
*/
|
||||
public class EncryptionChecker extends AbstractLifecycleBean
|
||||
{
|
||||
private TransactionService transactionService;
|
||||
private KeyStoreChecker keyStoreChecker;
|
||||
|
||||
public void setKeyStoreChecker(KeyStoreChecker keyStoreChecker)
|
||||
{
|
||||
this.keyStoreChecker = keyStoreChecker;
|
||||
}
|
||||
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent event)
|
||||
{
|
||||
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
||||
txnHelper.setForceWritable(true); // Force write in case server is read-only
|
||||
|
||||
txnHelper.doInTransaction(new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
try
|
||||
{
|
||||
keyStoreChecker.validateKeyStores();
|
||||
}
|
||||
catch(Throwable e)
|
||||
{
|
||||
// Just throw as a runtime exception
|
||||
throw new AlfrescoRuntimeException("Keystores are invalid", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent event)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.encryption;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
import org.alfresco.util.GUID;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Registered Encryption Keys are stored in the AttributeService directly under a top level key defined by
|
||||
* TOP_LEVEL_KEY (which means that all key aliases must be unique across however many keystores are being used).
|
||||
*
|
||||
* @since 4.0
|
||||
*
|
||||
*/
|
||||
// TODO caching? This will probably not be used extensively.
|
||||
// TODO instead of persisting the Pair when registering a key, create two attributes per key (one for the
|
||||
// guid and one for the encrypted value of the guid). This means a custom class does not need to be bound to
|
||||
// the attribute service.
|
||||
public class EncryptionKeysRegistryImpl implements EncryptionKeysRegistry
|
||||
{
|
||||
public static String TOP_LEVEL_KEY = "keyCheck";
|
||||
private static final Log logger = LogFactory.getLog(EncryptionKeysRegistryImpl.class);
|
||||
|
||||
private TransactionService transactionService;
|
||||
private AttributeService attributeService;
|
||||
private String cipherAlgorithm;
|
||||
private String cipherProvider;
|
||||
|
||||
public void setAttributeService(AttributeService attributeService)
|
||||
{
|
||||
this.attributeService = attributeService;
|
||||
}
|
||||
|
||||
public void setCipherAlgorithm(String cipherAlgorithm)
|
||||
{
|
||||
this.cipherAlgorithm = cipherAlgorithm;
|
||||
}
|
||||
|
||||
public void setCipherProvider(String cipherProvider)
|
||||
{
|
||||
this.cipherProvider = cipherProvider;
|
||||
}
|
||||
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
protected Encryptor getEncryptor(final KeyMap keys)
|
||||
{
|
||||
DefaultEncryptor encryptor = new DefaultEncryptor();
|
||||
encryptor.setCipherAlgorithm(cipherAlgorithm);
|
||||
encryptor.setCipherProvider(cipherProvider);
|
||||
encryptor.setKeyProvider(new KeyProvider()
|
||||
{
|
||||
@Override
|
||||
public Key getKey(String keyAlias)
|
||||
{
|
||||
return keys.getCachedKey(keyAlias).getKey();
|
||||
}
|
||||
});
|
||||
return encryptor;
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
|
||||
public void registerKey(String keyAlias, Key key)
|
||||
{
|
||||
if(isKeyRegistered(keyAlias))
|
||||
{
|
||||
throw new IllegalArgumentException("Key " + keyAlias + " is already registered");
|
||||
}
|
||||
|
||||
// register the key by creating an attribute that stores a guid and its encrypted value
|
||||
String guid = GUID.generate();
|
||||
|
||||
KeyMap keys = new KeyMap();
|
||||
keys.setKey(keyAlias, key);
|
||||
Encryptor encryptor = getEncryptor(keys);
|
||||
Serializable encrypted = encryptor.sealObject(keyAlias, null, guid);
|
||||
Pair<String, Serializable> keyCheck = new Pair<String, Serializable>(guid, encrypted);
|
||||
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
|
||||
final RetryingTransactionCallback<Void> createAttributeCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
attributeService.createAttribute(keyCheck, TOP_LEVEL_KEY, keyAlias);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
retryingTransactionHelper.doInTransaction(createAttributeCallback, false);
|
||||
|
||||
logger.info("Registered key " + keyAlias);
|
||||
}
|
||||
|
||||
public void unregisterKey(String keyAlias)
|
||||
{
|
||||
attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias);
|
||||
}
|
||||
|
||||
public boolean isKeyRegistered(String keyAlias)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias) != null);
|
||||
}
|
||||
catch(Throwable e)
|
||||
{
|
||||
// there is an issue getting the attribute. Remove it.
|
||||
attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias);
|
||||
return (attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias) != null);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getRegisteredKeys(final Set<String> keyStoreKeys)
|
||||
{
|
||||
final List<String> registeredKeys = new ArrayList<String>();
|
||||
|
||||
attributeService.getAttributes(new AttributeQueryCallback()
|
||||
{
|
||||
public boolean handleAttribute(Long id, Serializable value,
|
||||
Serializable[] keys)
|
||||
{
|
||||
// Add as a registered key if the keystore contains the key
|
||||
String keyAlias = (String)keys[1];
|
||||
if(keyStoreKeys.contains(keyAlias))
|
||||
{
|
||||
registeredKeys.add(keyAlias);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
},
|
||||
TOP_LEVEL_KEY);
|
||||
|
||||
return registeredKeys;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public KEY_STATUS checkKey(String keyAlias, Key key)
|
||||
{
|
||||
Pair<String, Serializable> keyCheck = null;
|
||||
|
||||
if(attributeService.exists(TOP_LEVEL_KEY, keyAlias))
|
||||
{
|
||||
try
|
||||
{
|
||||
// check that the key has not changed by decrypting the encrypted guid attribute
|
||||
// comparing against the guid
|
||||
try
|
||||
{
|
||||
keyCheck = (Pair<String, Serializable>)attributeService.getAttribute(TOP_LEVEL_KEY, keyAlias);
|
||||
}
|
||||
catch(Throwable e)
|
||||
{
|
||||
// there is an issue getting the attribute. Remove it.
|
||||
attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias);
|
||||
return KEY_STATUS.MISSING;
|
||||
}
|
||||
|
||||
if(keyCheck == null)
|
||||
{
|
||||
return KEY_STATUS.MISSING;
|
||||
}
|
||||
|
||||
KeyMap keys = new KeyMap();
|
||||
keys.setKey(keyAlias, key);
|
||||
Encryptor encryptor = getEncryptor(keys);
|
||||
Serializable storedGUID = encryptor.unsealObject(keyAlias, keyCheck.getSecond());
|
||||
return EqualsHelper.nullSafeEquals(storedGUID, keyCheck.getFirst()) ? KEY_STATUS.OK : KEY_STATUS.CHANGED;
|
||||
}
|
||||
catch(InvalidKeyException e)
|
||||
{
|
||||
// key exception indicates that the key has changed - it can't decrypt the
|
||||
// previously-encrypted data
|
||||
return KEY_STATUS.CHANGED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return KEY_STATUS.MISSING;
|
||||
}
|
||||
}
|
||||
|
||||
// note that this removes _all_ keys in the keystore. Use with care.
|
||||
public void removeRegisteredKeys(final Set<String> keys)
|
||||
{
|
||||
RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper();
|
||||
final RetryingTransactionCallback<Void> removeKeysCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
for(String keyAlias : keys)
|
||||
{
|
||||
attributeService.removeAttribute(TOP_LEVEL_KEY, keyAlias);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
retryingTransactionHelper.doInTransaction(removeKeysCallback, false);
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.encryption;
|
||||
|
||||
/**
|
||||
* Checks the repository key stores.
|
||||
*
|
||||
* @since 4.0
|
||||
*
|
||||
*/
|
||||
public class KeyStoreChecker
|
||||
{
|
||||
private AlfrescoKeyStore mainKeyStore;
|
||||
|
||||
public KeyStoreChecker()
|
||||
{
|
||||
}
|
||||
|
||||
public void setMainKeyStore(AlfrescoKeyStore mainKeyStore)
|
||||
{
|
||||
this.mainKeyStore = mainKeyStore;
|
||||
}
|
||||
|
||||
public void validateKeyStores() throws InvalidKeystoreException, MissingKeyException
|
||||
{
|
||||
mainKeyStore.validateKeys();
|
||||
if(!mainKeyStore.exists())
|
||||
{
|
||||
mainKeyStore.create();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.encryption;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.SealedObject;
|
||||
|
||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
|
||||
import org.alfresco.repo.batch.BatchProcessor;
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.domain.node.NodeDAO;
|
||||
import org.alfresco.repo.domain.node.NodePropertyEntity;
|
||||
import org.alfresco.repo.domain.node.NodePropertyKey;
|
||||
import org.alfresco.repo.domain.node.NodePropertyValue;
|
||||
import org.alfresco.repo.domain.qname.QNameDAO;
|
||||
import org.alfresco.repo.lock.JobLockService;
|
||||
import org.alfresco.repo.lock.LockAcquisitionException;
|
||||
import org.alfresco.repo.node.encryption.MetadataEncryptor;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* Re-encrypts encryptable repository properties using a new set of encryption keys.
|
||||
* Decrypts the repository properties using the default encryptor, falling back to
|
||||
* a backup decryptor (using the old encryption keys) if necessary, and then re-encrypts
|
||||
* the properties.
|
||||
*
|
||||
* Can run in one of two ways:
|
||||
*
|
||||
* <ul>
|
||||
* <li> during bootstrap.
|
||||
* <li> by using JMX (available only to Enterprise). In this case, the system can stay running while the re-encryption takes place.
|
||||
* </ul>
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ReEncryptor implements ApplicationContextAware
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(ReEncryptor.class);
|
||||
|
||||
private NodeDAO nodeDAO;
|
||||
private DictionaryDAO dictionaryDAO;
|
||||
private QNameDAO qnameDAO;
|
||||
|
||||
private MetadataEncryptor metadataEncryptor;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private TransactionService transactionService;
|
||||
private RetryingTransactionHelper transactionHelper;
|
||||
|
||||
private int numThreads;
|
||||
private int chunkSize;
|
||||
private boolean splitTxns = true;
|
||||
|
||||
private static final QName LOCK = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "OrphanReaper");
|
||||
private JobLockService jobLockService;
|
||||
|
||||
/**
|
||||
* Set the transaction provider so that each execution can be performed within a transaction
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
this.transactionHelper = transactionService.getRetryingTransactionHelper();
|
||||
}
|
||||
|
||||
public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor)
|
||||
{
|
||||
this.metadataEncryptor = metadataEncryptor;
|
||||
}
|
||||
|
||||
public MetadataEncryptor getMetadataEncryptor()
|
||||
{
|
||||
return metadataEncryptor;
|
||||
}
|
||||
|
||||
public void setJobLockService(JobLockService jobLockService)
|
||||
{
|
||||
this.jobLockService = jobLockService;
|
||||
}
|
||||
|
||||
public void setNumThreads(int numThreads)
|
||||
{
|
||||
this.numThreads = numThreads;
|
||||
}
|
||||
|
||||
public void setChunkSize(int chunkSize)
|
||||
{
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public void setSplitTxns(boolean splitTxns)
|
||||
{
|
||||
this.splitTxns = splitTxns;
|
||||
}
|
||||
|
||||
public void setNodeDAO(NodeDAO nodeDAO)
|
||||
{
|
||||
this.nodeDAO = nodeDAO;
|
||||
}
|
||||
|
||||
public void setDictionaryDAO(DictionaryDAO dictionaryDAO)
|
||||
{
|
||||
this.dictionaryDAO = dictionaryDAO;
|
||||
}
|
||||
|
||||
public void setQnameDAO(QNameDAO qnameDAO)
|
||||
{
|
||||
this.qnameDAO = qnameDAO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the lock. If the lock couldn't be taken, then <tt>null</tt> is returned.
|
||||
*
|
||||
* @return Returns the lock token or <tt>null</tt>
|
||||
*/
|
||||
private String getLock(long time)
|
||||
{
|
||||
try
|
||||
{
|
||||
return jobLockService.getLock(LOCK, time);
|
||||
}
|
||||
catch (LockAcquisitionException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the lock. If it fails, the current transaction is marked for rollback.
|
||||
*/
|
||||
private void refreshLock(String lockToken, long time)
|
||||
{
|
||||
if (lockToken == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Must provide existing lockToken");
|
||||
}
|
||||
jobLockService.refreshLock(lockToken, LOCK, time);
|
||||
}
|
||||
|
||||
protected void reEncryptProperties(final List<NodePropertyEntity> properties, final String lockToken)
|
||||
{
|
||||
final Iterator<NodePropertyEntity> it = properties.iterator();
|
||||
|
||||
// TODO use BatchProcessWorkerAdaptor?
|
||||
|
||||
BatchProcessor.BatchProcessWorker<NodePropertyEntity> worker = new BatchProcessor.BatchProcessWorker<NodePropertyEntity>()
|
||||
{
|
||||
public String getIdentifier(NodePropertyEntity entity)
|
||||
{
|
||||
return String.valueOf(entity.getNodeId());
|
||||
}
|
||||
|
||||
public void beforeProcess() throws Throwable
|
||||
{
|
||||
refreshLock(lockToken, chunkSize * 100L);
|
||||
}
|
||||
|
||||
public void afterProcess() throws Throwable
|
||||
{
|
||||
}
|
||||
|
||||
public void process(final NodePropertyEntity entity) throws Throwable
|
||||
{
|
||||
NodePropertyValue nodePropValue = entity.getValue();
|
||||
// TODO check that we have the correct type i.e. can be cast to Serializable
|
||||
Serializable value = nodePropValue.getSerializableValue();
|
||||
if(value instanceof SealedObject)
|
||||
{
|
||||
SealedObject sealed = (SealedObject)value;
|
||||
|
||||
NodePropertyKey propertyKey = entity.getKey();
|
||||
QName propertyQName = qnameDAO.getQName(propertyKey.getQnameId()).getSecond();
|
||||
|
||||
// decrypt...
|
||||
Serializable decrypted = metadataEncryptor.decrypt(propertyQName, sealed);
|
||||
|
||||
// ...and then re-encrypt. The new key will be used.
|
||||
Serializable resealed = metadataEncryptor.encrypt(propertyQName, decrypted);
|
||||
|
||||
// TODO update resealed using batch update?
|
||||
// does the node DAO do batch updating?
|
||||
nodeDAO.setNodeProperties(entity.getNodeId(), Collections.singletonMap(propertyQName, resealed));
|
||||
}
|
||||
else
|
||||
{
|
||||
NodePropertyKey nodeKey = entity.getKey();
|
||||
QName propertyQName = qnameDAO.getQName(nodeKey.getQnameId()).getSecond();
|
||||
logger.warn("Encountered an encrypted property that is not a SealedObject, for node id " +
|
||||
entity.getNodeId() + ", property " + propertyQName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BatchProcessWorkProvider<NodePropertyEntity> provider = new BatchProcessWorkProvider<NodePropertyEntity>()
|
||||
{
|
||||
@Override
|
||||
public int getTotalEstimatedWorkSize()
|
||||
{
|
||||
return properties.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<NodePropertyEntity> getNextWork()
|
||||
{
|
||||
List<NodePropertyEntity> sublist = new ArrayList<NodePropertyEntity>(chunkSize);
|
||||
|
||||
synchronized(it)
|
||||
{
|
||||
int count = 0;
|
||||
while(it.hasNext() && count < chunkSize)
|
||||
{
|
||||
sublist.add(it.next());
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return sublist;
|
||||
}
|
||||
};
|
||||
|
||||
new BatchProcessor<NodePropertyEntity>(
|
||||
"Reencryptor",
|
||||
transactionHelper,
|
||||
provider,
|
||||
numThreads, chunkSize,
|
||||
applicationContext,
|
||||
logger, 100).process(worker, splitTxns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-encrypt using the configured backup keystore to decrypt and the main keystore to encrypt
|
||||
*/
|
||||
public int bootstrapReEncrypt() throws MissingKeyException
|
||||
{
|
||||
if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA))
|
||||
{
|
||||
throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key");
|
||||
}
|
||||
return reEncrypt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-encrypt by decrypting using the configured keystore and encrypting using a keystore configured using the provided new key store parameters.
|
||||
* Called from e.g. JMX.
|
||||
*
|
||||
* Assumes that the main key store has been already been reloaded.
|
||||
*
|
||||
* Note: it is the responsibility of the end user to ensure that the underlying keystores have been set up appropriately
|
||||
* i.e. the old key store is backed up to the location defined by the property '${dir.keystore}/backup-keystore' and the new
|
||||
* key store replaces it. This can be done while the repository is running.
|
||||
*/
|
||||
public int reEncrypt() throws MissingKeyException
|
||||
{
|
||||
if(!metadataEncryptor.keyAvailable(KeyProvider.ALIAS_METADATA))
|
||||
{
|
||||
throw new MissingKeyException("Main key store is either not present or does not contain a metadata encryption key");
|
||||
}
|
||||
if(!metadataEncryptor.backupKeyAvailable(KeyProvider.ALIAS_METADATA))
|
||||
{
|
||||
throw new MissingKeyException("Backup key store is either not present or does not contain a metadata encryption key");
|
||||
}
|
||||
|
||||
int numProps = reEncryptImpl();
|
||||
return numProps;
|
||||
}
|
||||
|
||||
protected int reEncryptImpl()
|
||||
{
|
||||
// Take out a re-encryptor lock
|
||||
RetryingTransactionCallback<String> txnWork = new RetryingTransactionCallback<String>()
|
||||
{
|
||||
public String execute() throws Exception
|
||||
{
|
||||
String lockToken = getLock(20000L);
|
||||
return lockToken;
|
||||
}
|
||||
};
|
||||
|
||||
String lockToken = transactionService.getRetryingTransactionHelper().doInTransaction(txnWork, false, true);
|
||||
if(lockToken == null)
|
||||
{
|
||||
logger.warn("Can't get lock. Assume multiple re-encryptors ...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get encrypted properties
|
||||
Collection<PropertyDefinition> propertyDefs = dictionaryDAO.getPropertiesOfDataType(DataTypeDefinition.ENCRYPTED);
|
||||
Set<QName> qnames = new HashSet<QName>();
|
||||
for(PropertyDefinition propDef : propertyDefs)
|
||||
{
|
||||
qnames.add(propDef.getName());
|
||||
}
|
||||
|
||||
// TODO use callback mechanism, or select based on set of nodes?
|
||||
List<NodePropertyEntity> properties = nodeDAO.selectNodePropertiesByTypes(qnames);
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Found " + properties.size() + " properties to re-encrypt...");
|
||||
}
|
||||
|
||||
// reencrypt these properties TODO don't call if num props == 0
|
||||
reEncryptProperties(properties, lockToken);
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("...done re-encrypting.");
|
||||
}
|
||||
|
||||
return properties.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
|
||||
{
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
@@ -0,0 +1,860 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.filesys;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.filesys.alfresco.AlfrescoClientInfoFactory;
|
||||
import org.alfresco.filesys.alfresco.ExtendedDiskInterface;
|
||||
import org.alfresco.jlan.debug.DebugConfigSection;
|
||||
import org.alfresco.jlan.ftp.FTPConfigSection;
|
||||
import org.alfresco.jlan.netbios.NetBIOSName;
|
||||
import org.alfresco.jlan.netbios.NetBIOSNameList;
|
||||
import org.alfresco.jlan.netbios.NetBIOSSession;
|
||||
import org.alfresco.jlan.netbios.win32.Win32NetBIOS;
|
||||
import org.alfresco.jlan.oncrpc.nfs.NFSConfigSection;
|
||||
import org.alfresco.jlan.server.auth.ClientInfo;
|
||||
import org.alfresco.jlan.server.config.GlobalConfigSection;
|
||||
import org.alfresco.jlan.server.config.InvalidConfigurationException;
|
||||
import org.alfresco.jlan.server.config.ServerConfiguration;
|
||||
import org.alfresco.jlan.util.IPAddress;
|
||||
import org.alfresco.jlan.util.Platform;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.tenant.TenantService;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.cmr.security.AuthenticationService;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.extensions.config.element.GenericConfigElement;
|
||||
|
||||
/**
|
||||
* Alfresco File Server Configuration Bean Class
|
||||
*
|
||||
* @author gkspencer
|
||||
*/
|
||||
public abstract class AbstractServerConfigurationBean extends ServerConfiguration implements
|
||||
ExtendedServerConfigurationAccessor, ApplicationListener, ApplicationContextAware
|
||||
{
|
||||
|
||||
// Debug logging
|
||||
|
||||
protected static final Log logger = LogFactory.getLog("org.alfresco.fileserver");
|
||||
|
||||
// IP address representing null
|
||||
|
||||
public static final String BIND_TO_IGNORE = "0.0.0.0";
|
||||
|
||||
// FTP server debug type strings
|
||||
|
||||
protected static final String m_ftpDebugStr[] = { "STATE", "RXDATA", "TXDATA", "DUMPDATA", "SEARCH", "INFO", "FILE", "FILEIO", "ERROR", "PKTTYPE",
|
||||
"TIMING", "DATAPORT", "DIRECTORY", "SSL" };
|
||||
|
||||
// Default FTP server port
|
||||
|
||||
protected static final int DefaultFTPServerPort = 21;
|
||||
|
||||
// Default FTP server session timeout
|
||||
protected static final int DefaultFTPSrvSessionTimeout = 5000;
|
||||
|
||||
// Default FTP anonymous account name
|
||||
|
||||
protected static final String DefaultFTPAnonymousAccount = "anonymous";
|
||||
|
||||
// NFS server debug type strings
|
||||
|
||||
protected static final String m_nfsDebugStr[] = { "RXDATA", "TXDATA", "DUMPDATA", "SEARCH", "INFO", "FILE",
|
||||
"FILEIO", "ERROR", "TIMING", "DIRECTORY", "SESSION" };
|
||||
|
||||
// Default thread pool size
|
||||
|
||||
protected static final int DefaultThreadPoolInit = 25;
|
||||
protected static final int DefaultThreadPoolMax = 50;
|
||||
|
||||
// Default memory pool settings
|
||||
|
||||
protected static final int[] DefaultMemoryPoolBufSizes = { 256, 4096, 16384, 66000 };
|
||||
protected static final int[] DefaultMemoryPoolInitAlloc = { 20, 20, 5, 5 };
|
||||
protected static final int[] DefaultMemoryPoolMaxAlloc = { 100, 50, 50, 50 };
|
||||
|
||||
|
||||
// Memory pool allocation limits
|
||||
|
||||
protected static final int MemoryPoolMinimumAllocation = 5;
|
||||
protected static final int MemoryPoolMaximumAllocation = 500;
|
||||
|
||||
// Maximum session timeout
|
||||
|
||||
public static final int MaxSessionTimeout = 60 * 60; // 1 hour
|
||||
|
||||
// Disk interface to use for shared filesystems
|
||||
|
||||
private ExtendedDiskInterface m_repoDiskInterface;
|
||||
|
||||
// Runtime platform type
|
||||
|
||||
private Platform.Type m_platform = Platform.Type.Unchecked;
|
||||
|
||||
// flag to indicate successful initialization
|
||||
|
||||
private boolean m_initialised;
|
||||
|
||||
// Main authentication service, public API
|
||||
|
||||
private AuthenticationService m_authenticationService;
|
||||
|
||||
// Authentication component, for internal functions
|
||||
|
||||
protected AuthenticationComponent m_authenticationComponent;
|
||||
|
||||
// Various services
|
||||
|
||||
private NodeService m_nodeService;
|
||||
private PersonService m_personService;
|
||||
private TransactionService m_transactionService;
|
||||
protected TenantService m_tenantService;
|
||||
private SearchService m_searchService;
|
||||
private NamespaceService m_namespaceService;
|
||||
private AuthorityService m_authorityService;
|
||||
|
||||
// Local server name and domain/workgroup name
|
||||
|
||||
private String m_localName;
|
||||
private String m_localNameFull;
|
||||
private String m_localDomain;
|
||||
|
||||
// Disable use of native code on Windows, do not use any JNI calls
|
||||
|
||||
protected boolean m_disableNativeCode = false;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public AbstractServerConfigurationBean()
|
||||
{
|
||||
super ( "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param srvName String
|
||||
*/
|
||||
public AbstractServerConfigurationBean( String srvName)
|
||||
{
|
||||
super( srvName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authentication service
|
||||
*
|
||||
* @param authenticationService AuthenticationService
|
||||
*/
|
||||
public void setAuthenticationService(AuthenticationService authenticationService)
|
||||
{
|
||||
m_authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filesystem driver for the node service based filesystem
|
||||
*
|
||||
* @param diskInterface DiskInterface
|
||||
*/
|
||||
public void setDiskInterface(ExtendedDiskInterface diskInterface)
|
||||
{
|
||||
m_repoDiskInterface = diskInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authentication component
|
||||
*
|
||||
* @param component AuthenticationComponent
|
||||
*/
|
||||
public void setAuthenticationComponent(AuthenticationComponent component)
|
||||
{
|
||||
m_authenticationComponent = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the node service
|
||||
*
|
||||
* @param service NodeService
|
||||
*/
|
||||
public void setNodeService(NodeService service)
|
||||
{
|
||||
m_nodeService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the person service
|
||||
*
|
||||
* @param service PersonService
|
||||
*/
|
||||
public void setPersonService(PersonService service)
|
||||
{
|
||||
m_personService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction service
|
||||
*
|
||||
* @param service TransactionService
|
||||
*/
|
||||
public void setTransactionService(TransactionService service)
|
||||
{
|
||||
m_transactionService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tenant service
|
||||
*
|
||||
* @param tenantService TenantService
|
||||
*/
|
||||
public void setTenantService(TenantService tenantService)
|
||||
{
|
||||
m_tenantService = tenantService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the search service
|
||||
*
|
||||
* @param searchService SearchService
|
||||
*/
|
||||
public void setSearchService(SearchService searchService)
|
||||
{
|
||||
m_searchService = searchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the namespace service
|
||||
*
|
||||
* @param namespaceService NamespaceService
|
||||
*/
|
||||
public void setNamespaceService(NamespaceService namespaceService)
|
||||
{
|
||||
m_namespaceService = namespaceService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authority service
|
||||
*
|
||||
* @param authService AuthorityService
|
||||
*/
|
||||
public void setAuthorityService(AuthorityService authService)
|
||||
{
|
||||
m_authorityService = authService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the configuration has been initialized
|
||||
*
|
||||
* @return Returns true if the configuration was fully initialised
|
||||
*/
|
||||
public boolean isInitialised()
|
||||
{
|
||||
return m_initialised;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the FTP server is enabled
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean isFTPServerEnabled()
|
||||
{
|
||||
return hasConfigSection( FTPConfigSection.SectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the NFS server is enabled
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean isNFSServerEnabled()
|
||||
{
|
||||
return hasConfigSection( NFSConfigSection.SectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the repository disk interface to be used to create shares
|
||||
*
|
||||
* @return DiskInterface
|
||||
*/
|
||||
public final ExtendedDiskInterface getRepoDiskInterface()
|
||||
{
|
||||
return m_repoDiskInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the configuration using the configuration service
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
// Check that all required properties have been set
|
||||
|
||||
if (m_authenticationComponent == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'authenticationComponent' not set");
|
||||
}
|
||||
else if (m_authenticationService == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'authenticationService' not set");
|
||||
}
|
||||
else if (m_nodeService == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'nodeService' not set");
|
||||
}
|
||||
else if (m_personService == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'personService' not set");
|
||||
}
|
||||
else if (m_transactionService == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'transactionService' not set");
|
||||
}
|
||||
else if (m_repoDiskInterface == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'diskInterface' not set");
|
||||
}
|
||||
else if (m_authorityService == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Property 'authorityService' not set");
|
||||
}
|
||||
|
||||
// Set the platform type
|
||||
|
||||
determinePlatformType();
|
||||
|
||||
// Create the debug output configuration using a logger for all file server debug output
|
||||
|
||||
DebugConfigSection debugConfig = new DebugConfigSection( this);
|
||||
try
|
||||
{
|
||||
debugConfig.setDebug("org.alfresco.filesys.debug.FileServerDebugInterface", new GenericConfigElement( "params"));
|
||||
}
|
||||
catch ( InvalidConfigurationException ex)
|
||||
{
|
||||
}
|
||||
|
||||
// Create the global configuration and Alfresco configuration sections
|
||||
|
||||
new GlobalConfigSection( this);
|
||||
new AlfrescoConfigSection( this);
|
||||
|
||||
// Install the Alfresco client information factory
|
||||
|
||||
ClientInfo.setFactory( new AlfrescoClientInfoFactory());
|
||||
|
||||
// We need to check for a WINS server configuration in the CIFS server config section to initialize
|
||||
// the NetBIOS name lookups to use WINS rather broadcast lookups, which may be used to get the local
|
||||
// domain
|
||||
|
||||
try {
|
||||
|
||||
// Get the CIFS server config section and extract the WINS server config, if available
|
||||
|
||||
processWINSServerConfig();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
||||
// Configuration error
|
||||
|
||||
logger.error("File server configuration error (WINS), " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// Initialize the filesystems
|
||||
|
||||
try
|
||||
{
|
||||
// Process the core server configuration
|
||||
processCoreServerConfig();
|
||||
|
||||
// Process the security configuration
|
||||
processSecurityConfig();
|
||||
|
||||
// Process the filesystems configuration
|
||||
processFilesystemsConfig();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Configuration error
|
||||
throw new AlfrescoRuntimeException("File server configuration error, " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// Initialize the FTP server
|
||||
|
||||
try
|
||||
{
|
||||
// Process the FTP server configuration
|
||||
processFTPServerConfig();
|
||||
|
||||
// Log the successful startup
|
||||
|
||||
logger.info("FTP server " + (isFTPServerEnabled() ? "" : "NOT ") + "started");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Configuration error
|
||||
|
||||
logger.error("FTP server configuration error, " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void processCoreServerConfig() throws InvalidConfigurationException;
|
||||
|
||||
protected abstract void processSecurityConfig();
|
||||
|
||||
protected abstract void processFilesystemsConfig();
|
||||
|
||||
protected abstract void processFTPServerConfig();
|
||||
|
||||
protected void processWINSServerConfig() {}
|
||||
|
||||
/**
|
||||
* Close the configuration bean
|
||||
*/
|
||||
public final void closeConfiguration()
|
||||
{
|
||||
super.closeConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the platform type
|
||||
*/
|
||||
private final void determinePlatformType()
|
||||
{
|
||||
if ( m_platform == Platform.Type.Unchecked)
|
||||
m_platform = Platform.isPlatformType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the platforms attribute returning the set of platform ids
|
||||
*
|
||||
* @param platformStr String
|
||||
*/
|
||||
protected final EnumSet<Platform.Type> parsePlatformString(String platformStr)
|
||||
{
|
||||
// Split the platform string and build up a set of platform types
|
||||
|
||||
EnumSet<Platform.Type> platformTypes = EnumSet.noneOf(Platform.Type.class);
|
||||
if (platformStr == null || platformStr.length() == 0)
|
||||
return platformTypes;
|
||||
|
||||
StringTokenizer token = new StringTokenizer(platformStr.toUpperCase(Locale.ENGLISH), ",");
|
||||
String typ = null;
|
||||
|
||||
try
|
||||
{
|
||||
while (token.hasMoreTokens())
|
||||
{
|
||||
|
||||
// Get the current platform type string and validate
|
||||
|
||||
typ = token.nextToken().trim();
|
||||
Platform.Type platform = Platform.Type.valueOf(typ);
|
||||
|
||||
if (platform != Platform.Type.Unknown)
|
||||
platformTypes.add(platform);
|
||||
else
|
||||
throw new AlfrescoRuntimeException("Invalid platform type, " + typ);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Invalid platform type, " + typ);
|
||||
}
|
||||
|
||||
// Return the platform types
|
||||
|
||||
return platformTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local server name and optionally trim the domain name
|
||||
*
|
||||
* @param trimDomain boolean
|
||||
* @return String
|
||||
*/
|
||||
public final String getLocalServerName(boolean trimDomain)
|
||||
{
|
||||
// Use cached untrimmed version if necessary
|
||||
if (!trimDomain)
|
||||
{
|
||||
return getLocalServerName();
|
||||
}
|
||||
|
||||
// Check if the name has already been set
|
||||
if (m_localName != null)
|
||||
return m_localName;
|
||||
|
||||
// Find the local server name
|
||||
String srvName = getLocalServerName();
|
||||
|
||||
// Strip the domain name
|
||||
|
||||
if (trimDomain && srvName != null)
|
||||
{
|
||||
int pos = srvName.indexOf(".");
|
||||
if (pos != -1)
|
||||
srvName = srvName.substring(0, pos);
|
||||
}
|
||||
|
||||
// Save the local server name
|
||||
|
||||
m_localName = srvName;
|
||||
|
||||
// Return the local server name
|
||||
|
||||
return srvName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local server name (untrimmed)
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
private String getLocalServerName()
|
||||
{
|
||||
// Check if the name has already been set
|
||||
|
||||
if (m_localNameFull != null)
|
||||
return m_localNameFull;
|
||||
|
||||
// Find the local server name
|
||||
|
||||
String srvName = null;
|
||||
|
||||
if (getPlatformType() == Platform.Type.WINDOWS && !isNativeCodeDisabled())
|
||||
{
|
||||
// Get the local name via JNI
|
||||
|
||||
srvName = Win32NetBIOS.GetLocalNetBIOSName();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the DNS name of the local system
|
||||
|
||||
try
|
||||
{
|
||||
srvName = InetAddress.getLocalHost().getHostName();
|
||||
}
|
||||
catch (UnknownHostException ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// Save the local server name
|
||||
|
||||
m_localNameFull = srvName;
|
||||
|
||||
// Return the local server name
|
||||
|
||||
return srvName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local domain/workgroup name
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public final String getLocalDomainName()
|
||||
{
|
||||
// Check if the local domain has been set
|
||||
|
||||
if (m_localDomain != null)
|
||||
return m_localDomain;
|
||||
|
||||
// Find the local domain name
|
||||
|
||||
String domainName = null;
|
||||
|
||||
if (getPlatformType() == Platform.Type.WINDOWS && !isNativeCodeDisabled())
|
||||
{
|
||||
// Get the local domain/workgroup name via JNI
|
||||
|
||||
domainName = Win32NetBIOS.GetLocalDomainName();
|
||||
|
||||
// Debug
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Local domain name is " + domainName + " (via JNI)");
|
||||
}
|
||||
else
|
||||
{
|
||||
NetBIOSName nbName = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Try and find the browse master on the local network
|
||||
|
||||
nbName = NetBIOSSession.FindName(NetBIOSName.BrowseMasterName, NetBIOSName.BrowseMasterGroup, 5000);
|
||||
|
||||
// Log the browse master details
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Found browse master at " + nbName.getIPAddressString(0));
|
||||
|
||||
// Get the NetBIOS name list from the browse master
|
||||
|
||||
NetBIOSNameList nbNameList = NetBIOSSession.FindNamesForAddress(nbName.getIPAddressString(0));
|
||||
if (nbNameList != null)
|
||||
{
|
||||
nbName = nbNameList.findName(NetBIOSName.MasterBrowser, false);
|
||||
// Set the domain/workgroup name
|
||||
if (nbName != null)
|
||||
domainName = nbName.getName();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// Save the local domain name
|
||||
|
||||
m_localDomain = domainName;
|
||||
|
||||
// Return the local domain/workgroup name
|
||||
|
||||
return domainName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an adapter name string and return the matching address
|
||||
*
|
||||
* @param adapter String
|
||||
* @return InetAddress
|
||||
* @exception InvalidConfigurationException
|
||||
*/
|
||||
protected final InetAddress parseAdapterName(String adapter)
|
||||
throws InvalidConfigurationException {
|
||||
|
||||
NetworkInterface ni = null;
|
||||
|
||||
try {
|
||||
ni = NetworkInterface.getByName( adapter);
|
||||
}
|
||||
catch (SocketException ex) {
|
||||
throw new InvalidConfigurationException( "Invalid adapter name, " + adapter);
|
||||
}
|
||||
|
||||
if ( ni == null)
|
||||
throw new InvalidConfigurationException( "Invalid network adapter name, " + adapter);
|
||||
|
||||
// Get the IP address for the adapter
|
||||
|
||||
InetAddress adapAddr = null;
|
||||
Enumeration<InetAddress> addrEnum = ni.getInetAddresses();
|
||||
|
||||
while ( addrEnum.hasMoreElements() && adapAddr == null) {
|
||||
|
||||
// Get the current address
|
||||
|
||||
InetAddress addr = addrEnum.nextElement();
|
||||
if ( IPAddress.isNumericAddress( addr.getHostAddress()))
|
||||
adapAddr = addr;
|
||||
}
|
||||
|
||||
// Check if we found the IP address to bind to
|
||||
|
||||
if ( adapAddr == null)
|
||||
throw new InvalidConfigurationException( "Adapter " + adapter + " does not have a valid IP address");
|
||||
|
||||
// Return the adapter address
|
||||
|
||||
return adapAddr;
|
||||
}
|
||||
|
||||
private ApplicationContext applicationContext = null;
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
|
||||
*/
|
||||
public void onApplicationEvent(ApplicationEvent event)
|
||||
{
|
||||
if (event instanceof ContextRefreshedEvent)
|
||||
{
|
||||
ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event;
|
||||
ApplicationContext refreshContext = refreshEvent.getApplicationContext();
|
||||
if (refreshContext != null && refreshContext.equals(applicationContext))
|
||||
{
|
||||
// Initialize the bean
|
||||
|
||||
init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||
*/
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException
|
||||
{
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication service
|
||||
*
|
||||
* @return AuthenticationService
|
||||
*/
|
||||
protected final AuthenticationService getAuthenticationService()
|
||||
{
|
||||
return m_authenticationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication component
|
||||
*
|
||||
* @return AuthenticationComponent
|
||||
*/
|
||||
protected final AuthenticationComponent getAuthenticationComponent()
|
||||
{
|
||||
return m_authenticationComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node service
|
||||
*
|
||||
* @return NodeService
|
||||
*/
|
||||
protected final NodeService getNodeService()
|
||||
{
|
||||
return m_nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the person service
|
||||
*
|
||||
* @return PersonService
|
||||
*/
|
||||
protected final PersonService getPersonService()
|
||||
{
|
||||
return m_personService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction service
|
||||
*
|
||||
* @return TransactionService
|
||||
*/
|
||||
protected final TransactionService getTransactionService()
|
||||
{
|
||||
return m_transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tenant service
|
||||
*
|
||||
* @return TenantService
|
||||
*/
|
||||
protected final TenantService getTenantService()
|
||||
{
|
||||
return m_tenantService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the search service
|
||||
*
|
||||
* @return SearchService
|
||||
*/
|
||||
protected final SearchService getSearchService()
|
||||
{
|
||||
return m_searchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the namespace service
|
||||
*
|
||||
* @return NamespaceService
|
||||
*/
|
||||
protected final NamespaceService getNamespaceService()
|
||||
{
|
||||
return m_namespaceService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if native code calls are disabled
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean isNativeCodeDisabled()
|
||||
{
|
||||
return m_disableNativeCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the named bean
|
||||
*
|
||||
* @param beanName String
|
||||
* @return Object
|
||||
*/
|
||||
public final Object getBean( String beanName)
|
||||
{
|
||||
return applicationContext.getBean( beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the applicatin context
|
||||
*
|
||||
* @return ApplicationContext
|
||||
*/
|
||||
public final ApplicationContext getApplicationsContext()
|
||||
{
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authority service
|
||||
*
|
||||
* @return AuthorityService
|
||||
*/
|
||||
public final AuthorityService getAuthorityService()
|
||||
{
|
||||
return m_authorityService;
|
||||
}
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.filesys;
|
||||
|
||||
import org.alfresco.jlan.server.config.ConfigSection;
|
||||
import org.alfresco.jlan.server.filesys.DiskInterface;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.tenant.TenantService;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.cmr.security.AuthenticationService;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
|
||||
/**
|
||||
* Alfresco Configuration Section Class
|
||||
*
|
||||
* @author gkspencer
|
||||
*/
|
||||
public class AlfrescoConfigSection extends ConfigSection {
|
||||
|
||||
// Alfresco configuration section name
|
||||
|
||||
public static final String SectionName = "Alfresco";
|
||||
|
||||
// Disk interface to use for shared filesystems
|
||||
|
||||
private DiskInterface m_repoDiskInterface;
|
||||
|
||||
// Main authentication service, public API
|
||||
|
||||
private AuthenticationService m_authenticationService;
|
||||
|
||||
// Authentication component, for internal functions
|
||||
|
||||
private AuthenticationComponent m_authenticationComponent;
|
||||
|
||||
// Various services
|
||||
|
||||
private NodeService m_nodeService;
|
||||
private PersonService m_personService;
|
||||
private TransactionService m_transactionService;
|
||||
private TenantService m_tenantService;
|
||||
private SearchService m_searchService;
|
||||
private NamespaceService m_namespaceService;
|
||||
private AuthorityService m_authorityService;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param config ServerConfigurationBean
|
||||
*/
|
||||
public AlfrescoConfigSection(AbstractServerConfigurationBean config) {
|
||||
super( SectionName, config);
|
||||
|
||||
// Copy values from the server configuration bean
|
||||
|
||||
m_repoDiskInterface = config.getRepoDiskInterface();
|
||||
|
||||
m_authenticationService = config.getAuthenticationService();
|
||||
m_authenticationComponent = config.getAuthenticationComponent();
|
||||
|
||||
m_nodeService = config.getNodeService();
|
||||
m_personService = config.getPersonService();
|
||||
m_transactionService = config.getTransactionService();
|
||||
m_tenantService = config.getTenantService();
|
||||
m_searchService = config.getSearchService();
|
||||
m_namespaceService = config.getNamespaceService();
|
||||
m_authorityService = config.getAuthorityService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the repository disk interface to be used to create shares
|
||||
*
|
||||
* @return DiskInterface
|
||||
*/
|
||||
public final DiskInterface getRepoDiskInterface()
|
||||
{
|
||||
return m_repoDiskInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication service
|
||||
*
|
||||
* @return AuthenticationService
|
||||
*/
|
||||
public final AuthenticationService getAuthenticationService()
|
||||
{
|
||||
return m_authenticationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication component
|
||||
*
|
||||
* @return AuthenticationComponent
|
||||
*/
|
||||
public final AuthenticationComponent getAuthenticationComponent()
|
||||
{
|
||||
return m_authenticationComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node service
|
||||
*
|
||||
* @return NodeService
|
||||
*/
|
||||
public final NodeService getNodeService()
|
||||
{
|
||||
return m_nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the person service
|
||||
*
|
||||
* @return PersonService
|
||||
*/
|
||||
public final PersonService getPersonService()
|
||||
{
|
||||
return m_personService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction service
|
||||
*
|
||||
* @return TransactionService
|
||||
*/
|
||||
public final TransactionService getTransactionService()
|
||||
{
|
||||
return m_transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tenant service
|
||||
*
|
||||
* @return TenantService
|
||||
*/
|
||||
public final TenantService getTenantService()
|
||||
{
|
||||
return m_tenantService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the search service
|
||||
*
|
||||
* @return SearchService
|
||||
*/
|
||||
public final SearchService getSearchService()
|
||||
{
|
||||
return m_searchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the namespace service
|
||||
*
|
||||
* @return NamespaceService
|
||||
*/
|
||||
public final NamespaceService getNamespaceService()
|
||||
{
|
||||
return m_namespaceService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authority service
|
||||
*
|
||||
* @return AuthorityService
|
||||
*/
|
||||
public final AuthorityService getAuthorityService()
|
||||
{
|
||||
return m_authorityService;
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.filesys;
|
||||
|
||||
import org.alfresco.jlan.server.config.ServerConfigurationAccessor;
|
||||
|
||||
/**
|
||||
* An interface exposing some extended capabilities of the AbstractServerConfigurationBean.
|
||||
*
|
||||
* @author dward
|
||||
*/
|
||||
public interface ExtendedServerConfigurationAccessor extends ServerConfigurationAccessor
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the local server name and optionally trim the domain name
|
||||
*
|
||||
* @param trimDomain
|
||||
* boolean
|
||||
* @return String
|
||||
*/
|
||||
public String getLocalServerName(boolean trimDomain);
|
||||
|
||||
/**
|
||||
* Get the local domain/workgroup name
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getLocalDomainName();
|
||||
}
|
274
repository/src/main/java/org/alfresco/filesys/FTPServerBean.java
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* 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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.filesys;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.SocketException;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.jlan.ftp.FTPConfigSection;
|
||||
import org.alfresco.jlan.ftp.FTPServer;
|
||||
import org.alfresco.jlan.server.NetworkServer;
|
||||
import org.alfresco.jlan.server.config.ServerConfiguration;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* FTP Server Class
|
||||
*
|
||||
* <p>Create and start the server components required to run the FTP server.
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class FTPServerBean extends AbstractLifecycleBean
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog("org.alfresco.ftp.server");
|
||||
|
||||
// Server configuration and sections
|
||||
|
||||
private ServerConfiguration filesysConfig;
|
||||
private FTPConfigSection m_ftpConfig;
|
||||
|
||||
// The actual FTP server
|
||||
|
||||
private FTPServer ftpServer;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param serverConfig ServerConfiguration
|
||||
*/
|
||||
public FTPServerBean(ServerConfiguration serverConfig)
|
||||
{
|
||||
filesysConfig = serverConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the server configuration
|
||||
*
|
||||
* @return ServerConfiguration
|
||||
*/
|
||||
public final ServerConfiguration getConfiguration()
|
||||
{
|
||||
return filesysConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true if the server started up without any errors
|
||||
*/
|
||||
public boolean isStarted()
|
||||
{
|
||||
return (ftpServer != null && filesysConfig.isServerRunning("FTP"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the FTP server components
|
||||
*
|
||||
* @exception SocketException If a network error occurs
|
||||
* @exception IOException If an I/O error occurs
|
||||
*/
|
||||
public final void startServer() throws SocketException, IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create the FTP server, if enabled
|
||||
|
||||
m_ftpConfig = (FTPConfigSection) filesysConfig.getConfigSection( FTPConfigSection.SectionName);
|
||||
|
||||
if (m_ftpConfig != null)
|
||||
{
|
||||
// Create the FTP server
|
||||
|
||||
ftpServer = new FTPServer(filesysConfig);
|
||||
filesysConfig.addServer(ftpServer);
|
||||
}
|
||||
|
||||
|
||||
// Start the server
|
||||
if(ftpServer != null)
|
||||
{
|
||||
// Start the FTP server
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("Starting server " + ftpServer.getProtocolName() + " ...");
|
||||
|
||||
ftpServer.startServer();
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
ftpServer = null;
|
||||
throw new AlfrescoRuntimeException("Failed to start FTP Server", e);
|
||||
}
|
||||
// success
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the FTP server components
|
||||
*/
|
||||
public final void stopServer()
|
||||
{
|
||||
if (filesysConfig == null)
|
||||
{
|
||||
// initialisation failed
|
||||
return;
|
||||
}
|
||||
|
||||
// Shutdown the FTP server
|
||||
|
||||
if ( ftpServer != null)
|
||||
{
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("Shutting server " + ftpServer.getProtocolName() + " ...");
|
||||
|
||||
// Stop the server
|
||||
|
||||
ftpServer.shutdownServer(false);
|
||||
|
||||
// Remove the server from the global list
|
||||
|
||||
getConfiguration().removeServer(ftpServer.getProtocolName());
|
||||
ftpServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the FTP server directly
|
||||
*
|
||||
* @param args String[]
|
||||
*/
|
||||
public static void main(String[] args)
|
||||
{
|
||||
PrintStream out = System.out;
|
||||
|
||||
out.println("FTP Server Test");
|
||||
out.println("---------------");
|
||||
|
||||
ClassPathXmlApplicationContext ctx = null;
|
||||
try
|
||||
{
|
||||
// Create the configuration service in the same way that Spring creates it
|
||||
|
||||
ctx = new ClassPathXmlApplicationContext("alfresco/application-context.xml");
|
||||
|
||||
// Get the FTP server bean
|
||||
|
||||
FTPServerBean server = (FTPServerBean) ctx.getBean("ftpServer");
|
||||
if (server == null)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Server bean 'ftpServer' not defined");
|
||||
}
|
||||
|
||||
// Stop the CIFS server components, if running
|
||||
|
||||
NetworkServer srv = server.getConfiguration().findServer("SMB");
|
||||
if ( srv != null)
|
||||
srv.shutdownServer(true);
|
||||
|
||||
srv = server.getConfiguration().findServer("NetBIOS");
|
||||
if ( srv != null)
|
||||
srv.shutdownServer(true);
|
||||
|
||||
// Only wait for shutdown if the FTP server is enabled
|
||||
|
||||
if ( server.getConfiguration().hasConfigSection(FTPConfigSection.SectionName))
|
||||
{
|
||||
|
||||
// FTP server should have automatically started
|
||||
//
|
||||
// Wait for shutdown via the console
|
||||
|
||||
out.println("Enter 'x' to shutdown ...");
|
||||
boolean shutdown = false;
|
||||
|
||||
// Wait while the server runs, user may stop the server by typing a key
|
||||
|
||||
while (shutdown == false)
|
||||
{
|
||||
|
||||
// Wait for the user to enter the shutdown key
|
||||
|
||||
int ch = System.in.read();
|
||||
|
||||
if (ch == 'x' || ch == 'X')
|
||||
shutdown = true;
|
||||
|
||||
synchronized (server)
|
||||
{
|
||||
server.wait(20);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the server
|
||||
|
||||
server.stopServer();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ctx != null)
|
||||
{
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent event)
|
||||
{
|
||||
try
|
||||
{
|
||||
startServer();
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to start FTP server", e);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Failed to start FTP server", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent event)
|
||||
{
|
||||
stopServer();
|
||||
|
||||
// Clear the configuration
|
||||
filesysConfig = null;
|
||||
}
|
||||
|
||||
}
|