diff --git a/.env b/.env new file mode 100644 index 00000000..eb9f3d9e --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +CHANGELOG_START_TAG=ical4j-connector-2.0.0-alpha2 +CHANGELOG_END_TAG=HEAD + +GRADLE_VERSION=8.4 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..960d3044 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: benfortuna diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97ba5031..0879851a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,20 +5,29 @@ name: Java CI with Gradle on: [push] +permissions: read-all + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: 11 -# - name: Grant execute permission for gradlew -# run: chmod +x gradlew - - name: Build with Gradle -# run: ./gradlew build - uses: eskatos/gradle-command-action@v1 - with: - arguments: build + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Execute Gradle build + id: check + run: ./gradlew check --console=plain --warning-mode all + + - uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: test-report + path: "$GITHUB_WORKSPACE/ical4j-connector-dav/build/reports/tests" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..79e6f559 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ develop, master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop ] + schedule: + - cron: '20 13 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 00000000..c1ebd968 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,21 @@ +name: Create Release + +on: + push: + tags: + - "ical4j-connector-*" + - "!ical4j-connector-*-pre" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 +# - name: Generate changelog +# run: make changelog + - name: Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true +# body_path: CHANGELOG.md \ No newline at end of file diff --git a/.github/workflows/publish-snapshots.yml b/.github/workflows/publish-snapshots.yml index 19ddeeb2..1098453e 100644 --- a/.github/workflows/publish-snapshots.yml +++ b/.github/workflows/publish-snapshots.yml @@ -1,20 +1,40 @@ name: Publish snapshots -on: [push] +on: + push: + branches: + - 'develop' + - 'feature/refactor' + +permissions: + contents: read + checks: write + pull-requests: write jobs: - gradle: + test: + uses: ical4j/ical4j/.github/workflows/test.yml@develop + + publish: + name: Publish Artifact + needs: test + if: ${{ needs.test.result == 'success' }} runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: 11 - - uses: eskatos/gradle-command-action@v1 - with: - arguments: build -x test publish - env: - GPR_USERNAME: benfortuna - GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Execute Gradle build + run: ./gradlew build -x test publish + env: + GPR_USERNAME: benfortuna + GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MAVEN_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 00000000..6376b383 --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,55 @@ +name: Scorecards supply-chain security +on: + # Only the default branch is supported. + branch_protection_rule: + schedule: + - cron: '36 1 * * 3' + push: + branches: [ develop ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecards analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + actions: read + contents: read + + steps: + - name: "Checkout code" + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v3.0.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 # v1.0.4 + with: + results_file: results.sarif + results_format: sarif + # Read-only PAT token. To create it, + # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. + repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} + # Publish the results to enable scorecard badges. For more details, see + # https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories, `publish_results` will automatically be set to `false`, + # regardless of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). + - name: "Upload artifact" + uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 740dacdd..97a197da 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ ical4j-connector.iml build .gradle +.jpb out target +ical-local \ No newline at end of file diff --git a/.palantir/revapi.yml b/.palantir/revapi.yml new file mode 100644 index 00000000..7c642d2f --- /dev/null +++ b/.palantir/revapi.yml @@ -0,0 +1,595 @@ +acceptedBreaks: + "1.0.5": + org.mnode.ical4j:ical4j-connector: + - code: "java.class.kindChanged" + old: "class org.ical4j.connector.dav.property.BaseDavPropertyName" + new: "interface org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.class.kindChanged" + old: "class org.ical4j.connector.dav.property.CSDavPropertyName" + new: "interface org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.class.kindChanged" + old: "class org.ical4j.connector.dav.property.CalDavPropertyName" + new: "interface org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.class.kindChanged" + old: "class org.ical4j.connector.dav.property.CardDavPropertyName" + new: "interface org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.class.kindChanged" + old: "class org.ical4j.connector.dav.property.ICalPropertyName" + new: "interface org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.class.kindChanged" + old: "enum org.ical4j.connector.dav.PathResolver" + new: "interface org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.class.noLongerFinal" + old: "enum org.ical4j.connector.dav.PathResolver" + new: "interface org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.class.noLongerImplementsInterface" + old: "enum org.ical4j.connector.dav.PathResolver" + new: "interface org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.class.noLongerInheritsFromClass" + old: "enum org.ical4j.connector.dav.PathResolver" + new: "interface org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.class.nowAbstract" + old: "class org.ical4j.connector.dav.property.BaseDavPropertyName" + new: "interface org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.class.nowAbstract" + old: "class org.ical4j.connector.dav.property.CSDavPropertyName" + new: "interface org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.class.nowAbstract" + old: "class org.ical4j.connector.dav.property.CalDavPropertyName" + new: "interface org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.class.nowAbstract" + old: "class org.ical4j.connector.dav.property.CardDavPropertyName" + new: "interface org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.class.nowAbstract" + old: "class org.ical4j.connector.dav.property.ICalPropertyName" + new: "interface org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.class.nowAbstract" + old: "enum org.ical4j.connector.dav.PathResolver" + new: "interface org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.class.removed" + old: "class org.ical4j.connector.dav.DavClient" + justification: "major_refactor" + - code: "java.class.removed" + old: "class org.ical4j.connector.dav.MkCalendar" + justification: "major_refactor" + - code: "java.class.removed" + old: "class org.ical4j.connector.dav.method.CalDavMethods" + justification: "major_refactor" + - code: "java.class.removed" + old: "class org.ical4j.connector.dav.method.MkCalendarMethod" + justification: "major_refactor" + - code: "java.class.removed" + old: "class org.ical4j.connector.dav.method.PrincipalPropertySearchMethod" + justification: "major_refactor" + - code: "java.class.removed" + old: "class org.ical4j.connector.dav.method.PutMethod" + justification: "major_refactor" + - code: "java.class.removed" + old: "class org.ical4j.connector.dav.response.PropFindResponseHandler" + justification: "major_refactor" + - code: "java.class.removed" + old: "enum org.ical4j.connector.dav.enums.MediaType" + justification: "major_refactor" + - code: "java.class.removed" + old: "enum org.ical4j.connector.dav.enums.ResourceType" + justification: "major_refactor" + - code: "java.class.removed" + old: "enum org.ical4j.connector.dav.enums.SupportedFeature" + justification: "major_refactor" + - code: "java.class.removed" + old: "interface org.ical4j.connector.dav.CalDavConstants" + justification: "major_refactor" + - code: "java.class.removed" + old: "interface org.ical4j.connector.dav.DavConstants" + justification: "major_refactor" + - code: "java.class.removed" + old: "interface org.ical4j.connector.dav.ResponseHandler" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.BAIKAL" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.BEDEWORK" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.CALENDAR_SERVER" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.CGP" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.CHANDLER" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.DAVICAL" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.GCAL" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.GENERIC" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.ICAL_SERVER" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.KMS" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.ORACLE_CS" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.RADICALE" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.SOGO" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.PathResolver.ZIMBRA" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.method.ReportMethod.ADDRESSBOOK_QUERY" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.method.ReportMethod.CALENDAR_QUERY" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.method.ReportMethod.FREEBUSY_QUERY" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.property.BaseDavPropertyName.CURRENT_USER_PRIVILEGE_SET" + justification: "major_refactor" + - code: "java.field.removed" + old: "field org.ical4j.connector.dav.property.BaseDavPropertyName.RESOURCETYPE" + justification: "major_refactor" + - code: "java.method.addedToInterface" + new: "method java.lang.String org.ical4j.connector.dav.PathResolver::getRepositoryRoot(java.lang.String,\ + \ java.lang.String)" + justification: "major_refactor" + - code: "java.method.addedToInterface" + new: "method java.lang.String org.ical4j.connector.dav.PathResolver::getResourcePath(java.lang.String,\ + \ java.lang.String)" + justification: "major_refactor" + - code: "java.method.addedToInterface" + new: "method java.lang.String org.ical4j.connector.dav.PathResolver::getRootPath()" + justification: "major_refactor" + - code: "java.method.exception.checkedAdded" + old: "method org.ical4j.connector.dav.DavClient org.ical4j.connector.dav.DavClientFactory::newInstance(java.net.URL,\ + \ java.lang.String, java.lang.String)" + new: "method org.ical4j.connector.dav.DefaultDavClient org.ical4j.connector.dav.DavClientFactory::newInstance(java.lang.String)\ + \ throws java.net.MalformedURLException" + justification: "major_refactor" + - code: "java.method.exception.checkedRemoved" + old: "method C org.ical4j.connector.local.AbstractLocalObjectStore>::removeCollection(java.lang.String)\ + \ throws org.ical4j.connector.ObjectStoreException, org.ical4j.connector.ObjectNotFoundException" + new: "method C org.ical4j.connector.local.AbstractLocalObjectStore>::removeCollection(java.lang.String)" + justification: "major_refactor" + - code: "java.method.exception.checkedRemoved" + old: "method java.lang.String org.ical4j.connector.dav.CalDavCalendarStore::findCalendarHomeSet(java.lang.String)\ + \ throws java.io.IOException, org.apache.jackrabbit.webdav.DavException" + new: "method java.lang.String org.ical4j.connector.dav.CalDavCalendarStore::findCalendarHomeSet(java.lang.String)\ + \ throws java.io.IOException" + justification: "major_refactor" + - code: "java.method.exception.checkedRemoved" + old: "method java.lang.String org.ical4j.connector.dav.CardDavStore::findAddressBookHomeSet(java.lang.String)\ + \ throws javax.xml.parsers.ParserConfigurationException, java.io.IOException,\ + \ org.apache.jackrabbit.webdav.DavException" + new: "method java.lang.String org.ical4j.connector.dav.CardDavStore::findAddressBookHomeSet(java.lang.String)\ + \ throws java.io.IOException" + justification: "major_refactor" + - code: "java.method.exception.checkedRemoved" + old: "method java.util.List org.ical4j.connector.local.AbstractLocalObjectStore>::getCollections()\ + \ throws org.ical4j.connector.ObjectStoreException, org.ical4j.connector.ObjectNotFoundException" + new: "method java.util.List org.ical4j.connector.local.AbstractLocalObjectStore>::getCollections()" + justification: "major_refactor" + - code: "java.method.exception.checkedRemoved" + old: "method net.fortuna.ical4j.model.Calendar org.ical4j.connector.local.LocalCalendarCollection::export()\ + \ throws org.ical4j.connector.ObjectStoreException" + new: "method net.fortuna.ical4j.model.Calendar org.ical4j.connector.local.LocalCalendarCollection::export()" + justification: "major_refactor" + - code: "java.method.exception.checkedRemoved" + old: "method void org.ical4j.connector.local.AbstractLocalObjectStore>::disconnect()\ + \ throws org.ical4j.connector.ObjectStoreException" + new: "method void org.ical4j.connector.local.AbstractLocalObjectStore>::disconnect()" + justification: "major_refactor" + - code: "java.method.exception.checkedRemoved" + old: "method void org.ical4j.connector.local.LocalCalendarCollection::merge(net.fortuna.ical4j.model.Calendar)\ + \ throws org.ical4j.connector.FailedOperationException, org.ical4j.connector.ObjectStoreException" + new: "method void org.ical4j.connector.local.LocalCalendarCollection::merge(net.fortuna.ical4j.model.Calendar)" + justification: "major_refactor" + - code: "java.method.nowAbstract" + old: "method java.lang.String org.ical4j.connector.dav.PathResolver::getPrincipalPath(java.lang.String)" + new: "method java.lang.String org.ical4j.connector.dav.PathResolver::getPrincipalPath(java.lang.String)" + justification: "major_refactor" + - code: "java.method.numberOfParametersChanged" + old: "method org.ical4j.connector.dav.DavClient org.ical4j.connector.dav.DavClientFactory::newInstance(java.net.URL,\ + \ java.lang.String, java.lang.String)" + new: "method org.ical4j.connector.dav.DefaultDavClient org.ical4j.connector.dav.DavClientFactory::newInstance(java.lang.String)\ + \ throws java.net.MalformedURLException" + justification: "major_refactor" + - code: "java.method.numberOfParametersChanged" + old: "method void org.ical4j.connector.dav.DavClientFactory::(boolean)" + new: "method void org.ical4j.connector.dav.DavClientFactory::()" + justification: "major_refactor" + - code: "java.method.parameterTypeChanged" + old: "parameter java.util.List\ + \ org.ical4j.connector.dav.CalDavCalendarStore::getDelegatedCollections(===java.lang.String===)\ + \ throws java.lang.Exception" + new: "parameter java.util.List\ + \ org.ical4j.connector.dav.CalDavCalendarStore::getDelegatedCollections(===org.ical4j.connector.dav.request.ExpandPropertyQuery.Type===)\ + \ throws java.lang.Exception" + justification: "major_refactor" + - code: "java.method.removed" + old: "method >> T java.lang.Enum>>::valueOf(java.lang.Class,\ + \ java.lang.String) @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method boolean java.lang.Object::equals(java.lang.Object) @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method boolean java.lang.Object::equals(java.lang.Object) @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method boolean java.lang.Object::equals(java.lang.Object) @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method boolean java.lang.Object::equals(java.lang.Object) @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method boolean java.lang.Object::equals(java.lang.Object) @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method boolean java.lang.Object::equals(java.lang.Object) @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Enum>>::compareTo(E)\ + \ @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Enum>>::ordinal()\ + \ @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Object::hashCode() @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Object::hashCode() @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Object::hashCode() @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Object::hashCode() @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Object::hashCode() @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method int java.lang.Object::hashCode() @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Class java.lang.Object::getClass() @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Class java.lang.Object::getClass() @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Class java.lang.Object::getClass() @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Class java.lang.Object::getClass() @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Class java.lang.Object::getClass() @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Class java.lang.Object::getClass() @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Class java.lang.Enum>>::getDeclaringClass() @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Object java.lang.Object::clone() throws java.lang.CloneNotSupportedException\ + \ @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Object java.lang.Object::clone() throws java.lang.CloneNotSupportedException\ + \ @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Object java.lang.Object::clone() throws java.lang.CloneNotSupportedException\ + \ @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Object java.lang.Object::clone() throws java.lang.CloneNotSupportedException\ + \ @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Object java.lang.Object::clone() throws java.lang.CloneNotSupportedException\ + \ @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.Object java.lang.Object::clone() throws java.lang.CloneNotSupportedException\ + \ @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String java.lang.Enum>>::name() @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String java.lang.Object::toString() @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String java.lang.Object::toString() @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String java.lang.Object::toString() @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String java.lang.Object::toString() @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String java.lang.Object::toString() @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String java.lang.Object::toString() @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.lang.String org.ical4j.connector.dav.PathResolver::getUserPath(java.lang.String)" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.util.List\ + \ org.ical4j.connector.dav.CalDavCalendarStore::getDelegateCollections(org.apache.jackrabbit.webdav.property.DavProperty)\ + \ throws javax.xml.parsers.ParserConfigurationException, java.io.IOException,\ + \ org.apache.jackrabbit.webdav.DavException" + justification: "major_refactor" + - code: "java.method.removed" + old: "method java.util.List\ + \ org.ical4j.connector.dav.CardDavStore::getDelegateCollections(org.apache.jackrabbit.webdav.property.DavProperty)\ + \ throws javax.xml.parsers.ParserConfigurationException, java.io.IOException,\ + \ org.apache.jackrabbit.webdav.DavException" + justification: "major_refactor" + - code: "java.method.removed" + old: "method org.ical4j.connector.dav.PathResolver org.ical4j.connector.dav.PathResolver::valueOf(java.lang.String)" + justification: "major_refactor" + - code: "java.method.removed" + old: "method org.ical4j.connector.dav.PathResolver[] org.ical4j.connector.dav.PathResolver::values()" + justification: "major_refactor" + - code: "java.method.removed" + old: "method net.fortuna.ical4j.model.Calendar[] org.ical4j.connector.dav.CalDavCalendarCollection::getObjectsByFilter(org.w3c.dom.Element,\ + \ org.w3c.dom.Element) throws java.io.IOException, org.apache.jackrabbit.webdav.DavException,\ + \ javax.xml.parsers.ParserConfigurationException, net.fortuna.ical4j.data.ParserException" + justification: "major_refactor" + - code: "java.method.removed" + old: "method org.w3c.dom.Element org.ical4j.connector.dav.CalDavCalendarStore::propertiesForPropSearch(org.w3c.dom.Document)" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::finalize() throws java.lang.Throwable @\ + \ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::finalize() throws java.lang.Throwable @\ + \ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::finalize() throws java.lang.Throwable @\ + \ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::finalize() throws java.lang.Throwable @\ + \ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::finalize() throws java.lang.Throwable @\ + \ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::finalize() throws java.lang.Throwable @\ + \ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notify() @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notify() @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notify() @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notify() @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notify() @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notify() @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notifyAll() @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notifyAll() @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notifyAll() @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notifyAll() @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notifyAll() @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::notifyAll() @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait() throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait() throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait() throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait() throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait() throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait() throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long, int) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.PathResolver" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long, int) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.BaseDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long, int) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CSDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long, int) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CalDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long, int) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.CardDavPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void java.lang.Object::wait(long, int) throws java.lang.InterruptedException\ + \ @ org.ical4j.connector.dav.property.ICalPropertyName" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void org.ical4j.connector.dav.property.BaseDavPropertyName::()" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void org.ical4j.connector.dav.property.CSDavPropertyName::()" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void org.ical4j.connector.dav.property.CalDavPropertyName::()" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void org.ical4j.connector.dav.property.CardDavPropertyName::()" + justification: "major_refactor" + - code: "java.method.removed" + old: "method void org.ical4j.connector.dav.property.ICalPropertyName::()" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method java.util.ArrayList\ + \ org.ical4j.connector.dav.CalDavCalendarStore::findFreeBusyInfoForAttendees(net.fortuna.ical4j.model.property.Organizer,\ + \ java.util.ArrayList, net.fortuna.ical4j.model.property.DtStart,\ + \ net.fortuna.ical4j.model.property.DtEnd) throws javax.xml.parsers.ParserConfigurationException,\ + \ java.io.IOException, org.apache.jackrabbit.webdav.DavException, java.text.ParseException,\ + \ net.fortuna.ical4j.data.ParserException, org.xml.sax.SAXException" + new: "method java.util.List\ + \ org.ical4j.connector.dav.CalDavCalendarStore::findFreeBusyInfoForAttendees(net.fortuna.ical4j.model.property.Organizer,\ + \ java.util.ArrayList, net.fortuna.ical4j.model.property.DtStart,\ + \ net.fortuna.ical4j.model.property.DtEnd) throws javax.xml.parsers.ParserConfigurationException,\ + \ java.io.IOException, org.apache.jackrabbit.webdav.DavException, java.text.ParseException,\ + \ net.fortuna.ical4j.data.ParserException, org.xml.sax.SAXException" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method org.ical4j.connector.dav.DavClient org.ical4j.connector.dav.AbstractDavObjectStore>::getClient()" + new: "method org.ical4j.connector.dav.DefaultDavClient org.ical4j.connector.dav.AbstractDavObjectStore>::getClient()" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method org.ical4j.connector.dav.DavClient org.ical4j.connector.dav.DavClientFactory::newInstance(java.net.URL,\ + \ java.lang.String, java.lang.String)" + new: "method org.ical4j.connector.dav.DefaultDavClient org.ical4j.connector.dav.DavClientFactory::newInstance(java.lang.String)\ + \ throws java.net.MalformedURLException" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method org.ical4j.connector.dav.enums.MediaType[] org.ical4j.connector.CalendarCollection::getSupportedMediaTypes()" + new: "method org.ical4j.connector.MediaType[] org.ical4j.connector.CalendarCollection::getSupportedMediaTypes()" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method org.ical4j.connector.dav.enums.MediaType[] org.ical4j.connector.dav.AbstractDavObjectCollection::getSupportedMediaTypes()" + new: "method org.ical4j.connector.MediaType[] org.ical4j.connector.dav.AbstractDavObjectCollection::getSupportedMediaTypes()" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method org.ical4j.connector.dav.enums.MediaType[] org.ical4j.connector.jcr.JcrCalendarCollection::getSupportedMediaTypes()" + new: "method org.ical4j.connector.MediaType[] org.ical4j.connector.jcr.JcrCalendarCollection::getSupportedMediaTypes()" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method org.ical4j.connector.dav.enums.MediaType[] org.ical4j.connector.local.LocalCalendarCollection::getSupportedMediaTypes()" + new: "method org.ical4j.connector.MediaType[] org.ical4j.connector.local.LocalCalendarCollection::getSupportedMediaTypes()" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method org.ical4j.connector.dav.enums.ResourceType[] org.ical4j.connector.dav.AbstractDavObjectCollection::getResourceTypes()" + new: "method org.ical4j.connector.dav.ResourceType[] org.ical4j.connector.dav.AbstractDavObjectCollection::getResourceTypes()" + justification: "major_refactor" + - code: "java.method.returnTypeChanged" + old: "method net.fortuna.ical4j.model.Calendar[] org.ical4j.connector.dav.CalDavCalendarCollection::getEventsForTimePeriod(net.fortuna.ical4j.model.DateTime,\ + \ net.fortuna.ical4j.model.DateTime) throws java.io.IOException, org.apache.jackrabbit.webdav.DavException,\ + \ javax.xml.parsers.ParserConfigurationException, net.fortuna.ical4j.data.ParserException" + new: "method java.util.List org.ical4j.connector.dav.CalDavCalendarCollection::getEventsForTimePeriod(net.fortuna.ical4j.model.DateTime,\ + \ net.fortuna.ical4j.model.DateTime) throws java.io.IOException, org.apache.jackrabbit.webdav.DavException,\ + \ javax.xml.parsers.ParserConfigurationException, net.fortuna.ical4j.data.ParserException" + justification: "major_refactor" + - code: "java.method.returnTypeTypeParametersChanged" + old: "method java.util.List\ + \ org.ical4j.connector.dav.AbstractDavObjectStore>::supportedFeatures()" + new: "method java.util.List\ + \ org.ical4j.connector.dav.AbstractDavObjectStore>::supportedFeatures()" + justification: "major_refactor" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4196eced..00000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -#before_cache: -# - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock -# - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -cache: - directories: - #- $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ -sudo: false -dist: bionic -language: java -jdk: - - oraclejdk9 - - oraclejdk12 - - openjdk8 - - openjdk11 -env: - - GRADLE_OPTS="-Xmx512m -XX:MaxPermSize=256m" -script: - - ./gradlew -q build jacocoTestReport -after_success: - - bash <(curl -s https://codecov.io/bash) -deploy: - provider: script - script: ./gradlew -q -Prelease.customUsername=$GIT_USER -Prelease.customPassword=$GIT_PASSWORD -Prelease.disableChecks -Prelease.pushTagsOnly release && ./gradlew build bintrayUpload - on: - branch: master - jdk: openjdk8 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..fb8ce1c0 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +SHELL:=/bin/bash +include .env + +NEXT_VERSION=$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) +CHANGE_JUSTIFICATION=$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + +.PHONY: all gradlew clean check build changelog currentVersion markNextVersion listApiChanges approveApiChanges \ + verify release publish + +all: check + +gradlew: + ./gradlew wrapper --gradle-version=$(GRADLE_VERSION) --distribution-type=bin + +clean: + ./gradlew clean + +check: + ./gradlew check + +test: + ./gradlew test + +build: + ./gradlew build + +changelog: + git log "$(CHANGELOG_START_TAG)...$(CHANGELOG_END_TAG)" \ + --pretty=format:'* %s [View commit](http://github.com/ical4j/ical4j-connector/commit/%H)' --reverse | grep -v Merge + +currentVersion: + ./gradlew -q currentVersion + +markNextVersion: + ./gradlew markNextVersion -Prelease.version=$(NEXT_VERSION) + +listApiChanges: + ./gradlew revapi + +approveApiChanges: + ./gradlew :revapiAcceptAllBreaks --justification $(CHANGE_JUSTIFICATION) + +install: + ./gradlew publishToMavenLocal + +verify: + ./gradlew verify + +release: verify + ./gradlew release + +publish: + ./gradlew publish diff --git a/README.md b/README.md index b84deabb..c1b11d26 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,33 @@ -[![Build Status](https://drone.io/github.com/ical4j/ical4j-connector/status.png)](https://drone.io/github.com/ical4j/ical4j-connector/latest) +[Jackrabbit WebDAV]: https://jackrabbit.apache.org/jcr/components/jackrabbit-webdav-library.html +[DAVResource]: https://jackrabbit.apache.org/api/trunk/org/apache/jackrabbit/webdav/DavResource.html -================================== - iCal4j Connector - Release Notes -================================== +# iCal4j Connector - Support for iCalendar CalDAV/CardDAV specifications. See the website for details: - - http://ical4j.github.io/docs/ical4j-connector - +A client library with support for various iCalendar servers, including support for CalDAV. + +## Overview + +This library provides client support for backend calendar services using the iCal4j object model. The +most popular services implement DAV extensions for Calendaring (CalDAV) and VCard (CardDAV). + +Please see specific subprojects for implementation documentation. + +## Setup + +TBD. + +## Usage + +TBD. + +## References + +* [RFC3744](https://datatracker.ietf.org/doc/html/rfc3744) (WebDAV Access Control Protocol) +* [RFC4331](https://www.rfc-editor.org/rfc/rfc4331.html) (Quota and size properties for DAV) +* [RFC4791](https://www.rfc-editor.org/rfc/rfc4791.html) (CalDAV) +* [RFC4918](https://www.rfc-editor.org/rfc/rfc4918.html) (WebDAV) +* [RFC5397](https://www.rfc-editor.org/rfc/rfc5397.html) (WebDAV Current Principal Extension) +* [RFC5545](https://tools.ietf.org/html/rfc5545) (iCalendar) +* [RFC6350](https://datatracker.ietf.org/doc/html/rfc6350) (vCard) +* [RFC6352](https://www.rfc-editor.org/rfc/rfc6352.html) (CardDAV) +* [RFC7953](https://datatracker.ietf.org/doc/html/rfc7953) (iCalendar Availability) diff --git a/bnd.bnd b/bnd.bnd deleted file mode 100644 index 51bbd865..00000000 --- a/bnd.bnd +++ /dev/null @@ -1,2 +0,0 @@ -Export-Package: net.fortuna.ical4j.connector.* -Automatic-Module-Name: org.mnode.ical4j.connector diff --git a/build.gradle b/build.gradle index 74451ecd..a8f03fce 100644 --- a/build.gradle +++ b/build.gradle @@ -1,145 +1,147 @@ plugins { - id 'java' - id 'groovy' - id "biz.aQute.bnd.builder" version "5.1.2" - id 'java-library' - id 'maven-publish' id 'signing' - id 'pl.allegro.tech.build.axion-release' version '1.13.3' - id "nebula.optional-base" version "3.0.3" + id 'pl.allegro.tech.build.axion-release' version '1.13.6' + id 'biz.aQute.bnd.builder' version "$bndVersion" apply false } -repositories { - mavenCentral() - // sonatype snapshots - maven { - url 'https://oss.sonatype.org/content/repositories/snapshots/' +scmVersion { + tag { + prefix = 'ical4j-connector-' + } + versionCreator 'versionWithBranch' + branchVersionCreator = [ + 'master': 'simple', + 'develop': 'simple', + ] + nextVersion { + suffix = 'pre' + separator = '-' } } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -dependencies { - api "org.mnode.ical4j:ical4j:$ical4jVersion", - 'org.mnode.ical4j:ical4j-extensions:1.0.4', - "org.mnode.ical4j:ical4j-vcard:$ical4jVCardVersion", - 'commons-io:commons-io:2.7', - "org.apache.jackrabbit:jackrabbit-webdav:$jackrabbitWebdavVersion", - 'javax.jcr:jcr:2.0', - 'org.jcrom:jcrom:2.2.0', - "io.opentracing:opentracing-util:$openTracingVersion" +configure(subprojects) { + apply plugin: 'java-library' + apply plugin: 'maven-publish' + apply plugin: 'groovy' + apply plugin: 'biz.aQute.bnd.builder' - implementation "org.codehaus.groovy:groovy:$groovyVersion", optional + sourceCompatibility = 11 + targetCompatibility = 11 - compileOnly 'javax.servlet:servlet-api:2.5' - - testCompile 'org.apache.jackrabbit:jackrabbit-core:2.20.0', - "io.opentracing:opentracing-mock:$openTracingVersion" + repositories { + mavenCentral() + mavenLocal() + maven { + url 'https://oss.sonatype.org/content/repositories/snapshots' + } + } + group = 'org.ical4j' + description = '''\ +A Java library for accessing iCalendar data stores +''' + version = rootProject.scmVersion.version - // junit - testImplementation 'org.junit.vintage:junit-vintage-engine:5.7.1' + ext { + isReleaseVersion = !version.endsWith("SNAPSHOT") + } - // groovy-test - testImplementation "org.codehaus.groovy:groovy-test:$groovyVersion" + java { + withJavadocJar() + withSourcesJar() + } - // spock - testImplementation platform("org.spockframework:spock-bom:2.0-M4-groovy-3.0"), - "org.spockframework:spock-core", - "org.slf4j:slf4j-log4j12:$slf4jVersion" -} + dependencies { -test { - useJUnitPlatform() -} + annotationProcessor 'org.osgi:osgi.core:8.0.0', + 'org.osgi:org.osgi.service.component.annotations:1.5.0', + 'org.osgi:org.osgi.service.metatype.annotations:1.4.1', + 'org.osgi:org.osgi.annotation:6.0.0' -javadoc { - if (JavaVersion.current().isJava8Compatible()) { - options.addStringOption('Xdoclint:none', '-quiet') - } - options { - links 'https://docs.oracle.com/en/java/javase/11/docs/api/', - 'http://ical4j.github.io/docs/ical4j/api/3.1.2/' - } -} + // spock + testImplementation platform("org.spockframework:spock-bom:$spockVersion"), + 'commons-io:commons-io:2.11.0', + "org.spockframework:spock-core" -task javadocJar(type: Jar, dependsOn: javadoc) { - archiveClassifier.set('javadoc') - from 'build/docs/javadoc' -} + // junit +// testImplementation "org.junit.vintage:junit-vintage-engine:$junitVintageVersion" -task sourcesJar(type: Jar) { - from sourceSets.main.allSource - archiveClassifier.set('sources') -} + // testcontainers + testImplementation "org.testcontainers:testcontainers:$testcontainersVersion", + "org.testcontainers:spock:$testcontainersVersion" -artifacts { - archives javadocJar - archives sourcesJar -} - -scmVersion { - tag { - prefix = 'ical4j-connector-' + testImplementation "org.apache.logging.log4j:log4j-core:$log4jVersion", + "org.apache.logging.log4j:log4j-slf4j2-impl:$log4jVersion" } - versionCreator 'versionWithBranch' - branchVersionCreator = ['master': 'simple'] - nextVersion { - suffix = 'pre' - separator = '-' + + jar { + manifest { + attributes ( + 'Implementation-Title': 'iCal4j Connector', + 'Implementation-Version': archiveVersion, + 'Implementation-Vendor': 'Ben Fortuna' + ) + } } -} -group = 'org.mnode.ical4j' -description = 'A Java library for accessing iCalendar data stores' -version = scmVersion.version + test { + useJUnitPlatform() + } -ext { - isReleaseVersion = !version.endsWith("SNAPSHOT") -} + javadoc { + if (JavaVersion.current().isJava8Compatible()) { + options.addStringOption('Xdoclint:none', '-quiet') + } + options { + links 'https://docs.oracle.com/en/java/javase/11/docs/api/', + 'https://javadoc.io/doc/org.mnode.ical4j/ical4j/latest', + 'https://javadoc.io/doc/org.mnode.ical4j/ical4j-vcard/latest', + 'https://javadoc.io/doc/org.apache.httpcomponents/httpclient/latest', + 'https://javadoc.io/doc/org.apache.jackrabbit/jackrabbit-webdav/latest' + } + } -publishing { - publications { - ical4j_connector(MavenPublication) { - from components.java - artifact javadocJar - artifact sourcesJar - pom.withXml { - asNode().appendNode('name', project.name) - asNode().appendNode('description', project.description) - asNode().appendNode('url', 'http://ical4j.github.io') - - def scmNode = asNode().appendNode('scm') - scmNode.appendNode('url', 'https://github.com/ical4j/ical4j-connector') - scmNode.appendNode('connection', 'scm:git@github.com:ical4j/ical4j-connector.git') - scmNode.appendNode('developerConnection', 'scm:git@github.com:ical4j/ical4j-connector.git') - - def licenseNode = asNode().appendNode('licenses').appendNode('license') - licenseNode.appendNode('name', 'iCal4j - License') - licenseNode.appendNode('url', 'https://raw.githubusercontent.com/ical4j/ical4j-connector/master/LICENSE.txt') - licenseNode.appendNode('distribution', 'repo') - - def developerNode = asNode().appendNode('developers').appendNode('developer') - developerNode.appendNode('id', 'fortuna') - developerNode.appendNode('name', 'Ben Fortuna') + publishing { + publications { + "$name"(MavenPublication) { + from components.java + pom.withXml { + asNode().appendNode('name', name) + asNode().appendNode('description', description) + asNode().appendNode('url', 'http://ical4j.github.io') + + def scmNode = asNode().appendNode('scm') + scmNode.appendNode('url', 'https://github.com/ical4j/ical4j-connector') + scmNode.appendNode('connection', 'scm:git@github.com:ical4j/ical4j-connector.git') + scmNode.appendNode('developerConnection', 'scm:git@github.com:ical4j/ical4j-connector.git') + + def licenseNode = asNode().appendNode('licenses').appendNode('license') + licenseNode.appendNode('name', 'iCal4j - License') + licenseNode.appendNode('url', 'https://raw.githubusercontent.com/ical4j/ical4j/master/LICENSE') + licenseNode.appendNode('distribution', 'repo') + + def developerNode = asNode().appendNode('developers').appendNode('developer') + developerNode.appendNode('id', 'fortuna') + developerNode.appendNode('name', 'Ben Fortuna') + } } } - } - repositories { - maven { - name = "OSSRH" - url = !isReleaseVersion ? "https://oss.sonatype.org/content/repositories/snapshots/" : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - credentials { - username = System.getenv("MAVEN_USERNAME") - password = System.getenv("MAVEN_PASSWORD") + repositories { + maven { + name = "OSSRH" + url = version.endsWith('SNAPSHOT') ? "https://s01.oss.sonatype.org/content/repositories/snapshots/" \ + : "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + credentials { + username = System.getenv("MAVEN_USERNAME") + password = System.getenv("MAVEN_PASSWORD") + } } } } -} -signing { - required { isReleaseVersion } - sign publishing.publications.ical4j_connector + signing { + required { isReleaseVersion } + sign publishing.publications[name] + } } diff --git a/gradle.properties b/gradle.properties index 58220564..8e6ae54f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,19 @@ -ical4jVersion=3.1.3 -ical4jVCardVersion = 1.0.6 -jackrabbitWebdavVersion = 2.21.8 -groovyVersion=3.0.7 -slf4jVersion=1.7.30 +ical4jVersion = 4.0.8 +ical4jVCardVersion = 2.0.1 + +jacksonVersion = 2.17.1 +httpclientVersion = 4.5.14 +jackrabbitWebdavVersion = 2.23.1-beta +jackrabbitCoreVersion = 2.23.1-beta +groovyVersion=3.0.21 +slf4jVersion = 2.0.7 +log4jVersion = 2.20.0 openTracingVersion = 0.33.0 +jetbrainsVersion = 24.1.0 + +spockVersion = 2.4-M1-groovy-3.0 +junitVintageVersion = 5.8.2 +testcontainersVersion = 1.19.5 +bndVersion = 6.1.0 + +revApiOldVersion = 1.0.5 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053..943f0cbf 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bb8b2fc2..744c64d1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c515..65dcd68d 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 5093609d..93e3f59f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/ical4j-connector-api/README.md b/ical4j-connector-api/README.md new file mode 100644 index 00000000..ce5f141d --- /dev/null +++ b/ical4j-connector-api/README.md @@ -0,0 +1,66 @@ +# iCal4j Connector API + +The core module for iCal4j Connector. + +## Overview + +This library defines the base API for iCal4j Connector implementations, in addition to a REST API and +Command interface for manipulating iCalendar objects and collections. + +## Connector API + +The Connector API provides a common contract for connecting to iCalendar and vCard object stores. This API +is implemented by all the derivative modules providing connectivity to DAV servers, RDBMS, NoSQL, etc. + +### Local Store Implementation + +This library includes a Connector implementation supporting local file storage of iCalendar and vCard objects. +The local store may be used to manage a simple collection of vCard or iCalendar collections that are intended +to be accesses sequentially. + + +## Command Line Interface + +This library also provides support for the Command pattern for manipulating iCalendar and vCard collections and +objects. These commands are used as the basis for a command line interface that allows you to access and manipulate +iCalendar and vCard data via interactive shell environments. + +| Command | Description | Mandatory Arguments | +|-------------------|----------------------------------------------------------|------------------------| +| create-collection | Create new object collection in the configured store | Collection (JSON) | +| create-calendar | Create a new calendar object in the specified collection | Calendar (JSON) | +| create-card | Create a new card object in the specified collection | Card (JSON) | +| get-collection | | Collection ID | +| get-calendar | | Calendar UID | +| get-card | | Card UID | +| list-collections | | - | +| list-calendars | | - | +| list-cards | | - | +| update-collection | | Collection (JSON), UID | +| update-calendar | | Calendar (JSON), UID | +| update-card | | Card (JSON), UID | +| replace-calendar | Atomic equivalent to delete-calendar, create-calendar | Calendar (JSON), UID | +| replace-card | Atomic equivalent to delete-card, create-card | Card (JSON), UID | +| delete-collection | | Collection UID | +| delete-calendar | | Calendar UID | +| delete-card | | Card UID | + + +## REST API + +The includes REST API is designed to provide fine-grained access to iCalendar and vCard resources, suitable for +use as a backend for calendar and card-based applications. Using the same Command pattern support as the CLI, the +REST API provides additional support for manipulation of nested components and properties directly within iCalendar +and vCard objects and collections. + + | Resource | Description | HTTP Verbs | +|---------------------------------------------|------------------------------------------------------|-------------------------| +| /collections | List and create calendar and card collections | GET, POST | +| /collections/{uid} | Update and delete calendar and card collections | GET, PATCH, DELETE | +| /collections/{uid}/import | Import collection data from various formats | POST | +| /collections/{uid}/query | Query collections using filter expressions | GET, POST | +| /collections/{uid}/calendars | List and create calendars in a collection | GET, POST | +| /collections/{uid}/calendars/{uid} | Update, replace and delete calendars in a collection | GET, PATCH, PUT, DELETE | +| /collections/{uid}/cards | List and create cards in a collection | GET, POST | +| /collections/{uid}/cards/{uid} | Update, replace and delete cards in a collection | GET, PATCH, PUT, DELETE | + | /collections/{uid}/calendars/{uid}/freebusy | Query free or busy time for a calendar | GET, POST | diff --git a/ical4j-connector-api/build.gradle b/ical4j-connector-api/build.gradle new file mode 100644 index 00000000..324b8b77 --- /dev/null +++ b/ical4j-connector-api/build.gradle @@ -0,0 +1,4 @@ +dependencies { + api "org.mnode.ical4j:ical4j:$ical4jVersion", + "org.mnode.ical4j:ical4j-vcard:$ical4jVCardVersion" +} diff --git a/ical4j-connector-api/src/main/java/module-info.java b/ical4j-connector-api/src/main/java/module-info.java new file mode 100644 index 00000000..2a94666f --- /dev/null +++ b/ical4j-connector-api/src/main/java/module-info.java @@ -0,0 +1,13 @@ +module ical4j.connector.api { + requires java.base; + + requires transitive ical4j.core; + requires transitive ical4j.vcard; + + requires org.slf4j; + requires org.apache.commons.lang3; + + exports org.ical4j.connector; + exports org.ical4j.connector.local; + exports org.ical4j.connector.event; +} \ No newline at end of file diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/AbstractObjectCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/AbstractObjectCollection.java new file mode 100644 index 00000000..102066a6 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/AbstractObjectCollection.java @@ -0,0 +1,22 @@ +package org.ical4j.connector; + +import org.ical4j.connector.event.ListenerList; +import org.ical4j.connector.event.ObjectCollectionListener; + +public abstract class AbstractObjectCollection implements ObjectCollection { + + private final ListenerList> listenerList; + + public AbstractObjectCollection() { + this(new ListenerList<>()); + } + + public AbstractObjectCollection(ListenerList> listenerList) { + this.listenerList = listenerList; + } + + @Override + public ListenerList> getObjectCollectionListeners() { + return listenerList; + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/AbstractObjectStore.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/AbstractObjectStore.java new file mode 100644 index 00000000..798c1cda --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/AbstractObjectStore.java @@ -0,0 +1,25 @@ +package org.ical4j.connector; + +import org.ical4j.connector.event.ListenerList; +import org.ical4j.connector.event.ObjectStoreListener; + +public abstract class AbstractObjectStore> implements ObjectStore { + + /** + * Registered event listeners. + */ + private final ListenerList> listenerList; + + public AbstractObjectStore() { + this(new ListenerList<>()); + } + + public AbstractObjectStore(ListenerList> listenerList) { + this.listenerList = listenerList; + } + + @Override + public ListenerList> getObjectStoreListeners() { + return listenerList; + } +} diff --git a/src/main/java/net/fortuna/ical4j/connector/CalendarCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/CalendarCollection.java similarity index 68% rename from src/main/java/net/fortuna/ical4j/connector/CalendarCollection.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/CalendarCollection.java index ea0afb3e..bbe38dfd 100644 --- a/src/main/java/net/fortuna/ical4j/connector/CalendarCollection.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/CalendarCollection.java @@ -29,11 +29,22 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector; -import net.fortuna.ical4j.connector.dav.enums.MediaType; +import net.fortuna.ical4j.filter.ComponentFilter; +import net.fortuna.ical4j.filter.FilterExpression; import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.Component; import net.fortuna.ical4j.model.ConstraintViolationException; +import net.fortuna.ical4j.model.component.VFreeBusy; +import net.fortuna.ical4j.model.property.Uid; +import net.fortuna.ical4j.util.Calendars; + +import java.time.Instant; +import java.time.temporal.Temporal; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; /** * $Id$ @@ -80,14 +91,14 @@ public interface CalendarCollection extends ObjectCollection { * that the server is willing to accept for any DATE or DATE-TIME value in * a calendar object resource stored in a calendar collection. */ - String getMinDateTime(); + Instant getMinDateTime(); /** * @return a DATE-TIME value indicating the latest date and time (in UTC) * that the server is willing to accept for any DATE or DATE-TIME value in * a calendar object resource stored in a calendar collection. */ - String getMaxDateTime(); + Instant getMaxDateTime(); /** * @return a numeric value indicating the maximum number of recurrence instances @@ -100,42 +111,76 @@ public interface CalendarCollection extends ObjectCollection { * in any instance of a calendar object resource stored in a calendar collection. */ Integer getMaxAttendeesPerInstance(); + + default Calendar getFreeBusy(Temporal start, Temporal end) { + //XXX: construct from all events in the collection.. + return Calendars.wrap(new VFreeBusy()); + } /** * Stores the specified calendar in this collection. * @param calendar a calendar object instance to be added to the collection + * @return the UID extracted from the specified calendar * @throws ObjectStoreException when an unexpected error occurs (implementation-specific) * @throws ConstraintViolationException if the specified calendar has no single unique identifier (UID) + * @deprecated use {@link ObjectCollection#add(Object)} */ - void addCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException; + @Deprecated + default Uid addCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { + return new Uid(add(calendar)); + } /** * Returns the calendar object with the specified UID. * @param uid the UID associated with the returned calendar * @return a calendar object or null if no calendar with the specified UID exists + * @deprecated use {@link ObjectCollection#getAll(String...)} */ - Calendar getCalendar(String uid) throws ObjectNotFoundException; - + @Deprecated + default Calendar getCalendar(String uid) throws ObjectNotFoundException { + return get(uid).orElse(null); + } + + /** + * + * @param uids + * @return + * @deprecated use {@link ObjectCollection#getAll(String...)} + */ + @Deprecated + default List getCalendars(String... uids) { + return getAll(uids); + } + /** * @param uid the UID of the calendar to remove * @return the calendar that was successfully removed from the collection - * @throws ObjectStoreException where an unexpected error occurs + * @deprecated use {@link ObjectCollection#removeAll(String...)} */ - Calendar removeCalendar(String uid) throws FailedOperationException, ObjectStoreException, ObjectNotFoundException; + @Deprecated + default Calendar removeCalendar(String uid) throws FailedOperationException { + return removeAll(uid).get(0); + } /** - * Merges the specified calendar object with this collecton. This is done by + * Merges the specified calendar object with this collection. This is done by * decomposing the calendar object into a set of objects per unique identifier (UID) * and adding these objects to the collection. * @param calendar a calendar object instance to merge into the collection + * @return a list of UIDs extracted from the specified calendar * @throws FailedOperationException where the merge operation fails */ - void merge(Calendar calendar) throws FailedOperationException, ObjectStoreException; - + Uid[] merge(Calendar calendar) throws FailedOperationException, ObjectStoreException; + + @Override + default List query(FilterExpression filterExpression) { + Predicate filter = new ComponentFilter<>().predicate(filterExpression); + return getAll().stream().filter(c -> c.getComponents().stream().anyMatch(filter)).collect(Collectors.toList()); + } + /** * Exports the entire collection as a single calendar object. * @return a calendar object instance that contains all calendars in the collection - * @throws ObjectStoreException where an unexpected error occurs */ - Calendar export() throws ObjectStoreException; + Calendar export(); } diff --git a/src/main/java/net/fortuna/ical4j/connector/CalendarStore.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/CalendarStore.java similarity index 95% rename from src/main/java/net/fortuna/ical4j/connector/CalendarStore.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/CalendarStore.java index 63c1df8d..a9aee222 100644 --- a/src/main/java/net/fortuna/ical4j/connector/CalendarStore.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/CalendarStore.java @@ -29,9 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector; +import net.fortuna.ical4j.model.Calendar; /** * Design contract for calendar collection stores. @@ -45,7 +46,8 @@ * @author Ben * */ -public interface CalendarStore extends ObjectStore { +@Deprecated +public interface CalendarStore extends ObjectStore { /** * Merges the specified calendar with an existing calendar in the store with the diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/CardCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/CardCollection.java new file mode 100644 index 00000000..3f62d54d --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/CardCollection.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector; + +import net.fortuna.ical4j.filter.FilterExpression; +import net.fortuna.ical4j.model.ConstraintViolationException; +import net.fortuna.ical4j.vcard.Entity; +import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.filter.EntityFilter; +import net.fortuna.ical4j.vcard.property.Uid; + +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * $Id$ + * + * Created on 27/09/2008 + * + * @author Ben + * + */ +public interface CardCollection extends ObjectCollection { + + /** + * @param card a vCard object instance + * @return the UID extracted from the vCard + * @throws ObjectStoreException where an unexpected error occurs + * @throws ConstraintViolationException where the specified object is not valid + * @deprecated use {@link ObjectCollection#add(Object)} + */ + @Deprecated + default Uid addCard(VCard card) throws ObjectStoreException, ConstraintViolationException { + return new Uid(add(card)); + } + + /** + * + * @param uid + * @return + * @throws ObjectNotFoundException + * @throws FailedOperationException + * @deprecated use {@link ObjectCollection#getAll(String...)} + */ + @Deprecated + default VCard getCard(String uid) throws ObjectNotFoundException, FailedOperationException { + Optional card = get(uid); + return card.orElse(null); + } + + /** + * Remove an existing card from the collection. + * + * @param uid the uid of the existing card + * @return the card object that was removed from the collection + * @throws ObjectNotFoundException + * @throws FailedOperationException + * @deprecated use {@link ObjectCollection#removeAll(String...)} + * + */ + @Deprecated + default VCard removeCard(String uid) throws ObjectNotFoundException, FailedOperationException { + List result = removeAll(uid); + return result.get(0); + } + + /** + * + * @param card + * @return a list of UIDs extracted from the vCard data + * @throws ObjectStoreException + * @throws ConstraintViolationException + */ + Uid[] merge(VCard card) throws ObjectStoreException, ConstraintViolationException, FailedOperationException; + + @Override + default List query(FilterExpression filterExpression) { + Predicate filter = new EntityFilter().predicate(filterExpression); + return getAll().stream().filter(c -> c.getEntities().stream().anyMatch(filter)).collect(Collectors.toList()); + } + + /** + * Exports the entire collection as an array of objects. + * @return a vCard object array that contains all cards in the collection + */ + VCard export(); +} diff --git a/src/main/java/net/fortuna/ical4j/connector/CardStore.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/CardStore.java similarity index 94% rename from src/main/java/net/fortuna/ical4j/connector/CardStore.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/CardStore.java index 1b1523cf..93ee951d 100644 --- a/src/main/java/net/fortuna/ical4j/connector/CardStore.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/CardStore.java @@ -29,9 +29,11 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector; +import net.fortuna.ical4j.vcard.VCard; + /** * @param the collection type supported by the store * @@ -42,6 +44,7 @@ * @author Ben * */ -public interface CardStore extends ObjectStore { +@Deprecated +public interface CardStore extends ObjectStore { } diff --git a/src/main/java/net/fortuna/ical4j/connector/FailedOperationException.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/FailedOperationException.java similarity index 98% rename from src/main/java/net/fortuna/ical4j/connector/FailedOperationException.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/FailedOperationException.java index abb8abe9..ad0ddb59 100644 --- a/src/main/java/net/fortuna/ical4j/connector/FailedOperationException.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/FailedOperationException.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector; /** * diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/enums/MediaType.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/MediaType.java similarity index 91% rename from src/main/java/net/fortuna/ical4j/connector/dav/enums/MediaType.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/MediaType.java index 55a62a85..bd32c225 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/enums/MediaType.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/MediaType.java @@ -29,13 +29,11 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.enums; +package org.ical4j.connector; import java.util.HashSet; import java.util.Set; -import net.fortuna.ical4j.connector.CalendarCollection; - /** * $Id$ * @@ -61,14 +59,14 @@ public enum MediaType { private String version; - private static Set index = new HashSet(); + private static Set index = new HashSet<>(); static { - for (MediaType mediaType : MediaType.values()) { + for (var mediaType : MediaType.values()) { index.add(mediaType.getContentType()); } } - + /** * @param contentType * @param version @@ -93,7 +91,7 @@ public String getVersion() { } public static MediaType findByContentTypeAndVersion(String contentType, String version) { - for (MediaType feature : values()) { + for (var feature : values()) { if ((feature.getContentType().equals(contentType)) && (feature.getVersion().equals(version))) { return feature; } diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectCollection.java new file mode 100644 index 00000000..5ba5ffce --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectCollection.java @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector; + + +import net.fortuna.ical4j.filter.FilterExpression; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Implementors provide support for a persistent collection of objects. A collection will typically support + * most CRUD operations as well as filter queries. + * + * @param the object type stored by the collection + * + * $Id$ + * + * Created on 27/09/2008 + * + * @author Ben + * + */ +public interface ObjectCollection extends ObjectCollectionListenerSupport { + + String DEFAULT_COLLECTION = "default"; + + /** + * @return the collection name + */ + String getDisplayName(); + + /** + * @return the collection description + */ + String getDescription(); + + /** + * Return a list of object identifiers in the collection + * @return a list of object identifiers + */ + List listObjectUIDs(); + + /** + * Returns a list of objects found with the specified UIDs. Where no UID is specified all objects + * are returned. + * @param uid zero or more + * @return a list of all found objects + */ + default List getAll(String...uid) { + List result = new ArrayList<>(); + if (uid.length > 0) { + for (String u : uid) { + Optional cal = get(u); + cal.ifPresent(result::add); + } + } else { + for (String u : listObjectUIDs()) { + Optional cal = get(u); + cal.ifPresent(result::add); + } + } + return result; + } + + /** + * Return a single object with the specified UID if it exists. + * @param uid + * @return an optional reference to an existing object + */ + Optional get(String uid); + + /** + * Add a single object entity identified by an embedded UID value. + * @param object + * @return the UID discovered in the object entity + * @throws ObjectStoreException + */ + String add(T object) throws ObjectStoreException; + + /** + * Remove one or more objects found matching the specified UIDs. Where no UID is specified no + * action will be performed. + * + * Removal of all objects from a collection may be achieved as follows: + * + * collection.removeAll(collection.listObjectUIDs().toArray(new String[0])) + * + * @param uid + * @return a list of found objects that were removed + */ + List removeAll(String...uid) throws FailedOperationException; + + /** + * Returns a subset of objects that satisfy the specified filter expression. + * @param filterExpression an iCal4j component filter expression + * @return an iterable of objects matching the specified filter expressions + * @throws ObjectStoreException when an unexpected error occurs. + */ + default List query(FilterExpression filterExpression) { + throw new UnsupportedOperationException("Collection filtering not yet supported"); + } + + /** + * Returns a property value for the collection. + * @param the property return type + * @param name the property name + * @param type the property return type + * @return a value of the specified type, or null if no property is found + */ +// T getProperty(String propertyName, Class type); + + /** + * Remove the collection from the underlying storage implementation. + */ + void delete() throws ObjectStoreException; +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectCollectionListenerSupport.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectCollectionListenerSupport.java new file mode 100644 index 00000000..3b9a24ab --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectCollectionListenerSupport.java @@ -0,0 +1,49 @@ +package org.ical4j.connector; + +import org.ical4j.connector.event.ListenerList; +import org.ical4j.connector.event.ObjectCollectionEvent; +import org.ical4j.connector.event.ObjectCollectionListener; + +/** + * Provide support for notifying collection listeners of events. + */ +public interface ObjectCollectionListenerSupport { + + ListenerList> getObjectCollectionListeners(); + + /** + * Register listener for collection events. + * @param listener a collection listener + */ + default void addObjectCollectionListener(ObjectCollectionListener listener) { + getObjectCollectionListeners().add(listener); + } + + /** + * Unregister listener for collection events. + * @param listener a collection listener + */ + default void removeObjectCollectionListener(ObjectCollectionListener listener) { + getObjectCollectionListeners().remove(listener); + } + + default void fireOnAddEvent(ObjectCollection source, T object) { + getObjectCollectionListeners().getAll().forEach(listener -> listener.onAdd( + new ObjectCollectionEvent<>(source, object))); + } + + default void fireOnRemoveEvent(ObjectCollection source, T object) { + getObjectCollectionListeners().getAll().forEach(listener -> listener.onRemove( + new ObjectCollectionEvent<>(source, object))); + } + + default void fireOnMergeEvent(ObjectCollection source, T object) { + getObjectCollectionListeners().getAll().forEach(listener -> listener.onMerge( + new ObjectCollectionEvent<>(source, object))); + } + + default void fireOnReplaceEvent(ObjectCollection source, T object) { + getObjectCollectionListeners().getAll().forEach(listener -> listener.onReplace( + new ObjectCollectionEvent<>(source, object))); + } +} diff --git a/src/main/java/net/fortuna/ical4j/connector/ObjectNotFoundException.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectNotFoundException.java similarity index 98% rename from src/main/java/net/fortuna/ical4j/connector/ObjectNotFoundException.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectNotFoundException.java index b60b0b0d..c826cc7c 100644 --- a/src/main/java/net/fortuna/ical4j/connector/ObjectNotFoundException.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectNotFoundException.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector; /** * $Id$ diff --git a/src/main/java/net/fortuna/ical4j/connector/ObjectStore.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStore.java similarity index 82% rename from src/main/java/net/fortuna/ical4j/connector/ObjectStore.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStore.java index a129ac1a..5c7002bb 100644 --- a/src/main/java/net/fortuna/ical4j/connector/ObjectStore.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStore.java @@ -29,13 +29,16 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; - -import java.util.List; +package org.ical4j.connector; import net.fortuna.ical4j.model.Calendar; +import java.util.List; + /** + * Implementors provide support for management of persistent object collections. Typically, this will include + * most CRUD operations as well as caller authentication. + * * @param the type of collection supported by the store * * $Id$ @@ -45,7 +48,9 @@ * @author Ben * */ -public interface ObjectStore> { +public interface ObjectStore> extends ObjectStoreListenerSupport { + + String DEFAULT_WORKSPACE = "default"; /** * Connect to a object store anonymously. @@ -78,7 +83,9 @@ public interface ObjectStore> { * exists in the store */ C addCollection(String id) throws ObjectStoreException; - + + C addCollection(String id, String workspace) throws ObjectStoreException; + /** * @param id a collection identifier * @param displayName the collection name @@ -90,7 +97,10 @@ public interface ObjectStore> { */ C addCollection(String id, String displayName, String description, String[] supportedComponents, Calendar timezone) throws ObjectStoreException; - + + C addCollection(String id, String displayName, String description, + String[] supportedComponents, Calendar timezone, String workspace) throws ObjectStoreException; + /** * Removes the collection with specified id from the store. * @param id a collection identifier @@ -98,9 +108,11 @@ C addCollection(String id, String displayName, String description, * returned. Otherwise returns null. * @throws ObjectStoreException where an unexpected error occurs * @throws ObjectNotFoundException if a collection with the specified identifier doesn't exist + * @deprecated use {@link ObjectCollection#delete()} instead */ + @Deprecated C removeCollection(String id) throws ObjectStoreException, ObjectNotFoundException; - + /** * @param id a collection identifier * @return an object collection with the specified id. If no collection with the specified id @@ -110,5 +122,11 @@ C addCollection(String id, String displayName, String description, */ C getCollection(String id) throws ObjectStoreException, ObjectNotFoundException; + C getCollection(String id, String workspace) throws ObjectStoreException, ObjectNotFoundException; + List getCollections() throws ObjectStoreException, ObjectNotFoundException; + + List getCollections(String workspace) throws ObjectStoreException, ObjectNotFoundException; + + List listWorkspaceIds(); } diff --git a/src/main/java/net/fortuna/ical4j/connector/ObjectStoreException.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStoreException.java similarity index 98% rename from src/main/java/net/fortuna/ical4j/connector/ObjectStoreException.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStoreException.java index 3bded67a..44c4ba7f 100644 --- a/src/main/java/net/fortuna/ical4j/connector/ObjectStoreException.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStoreException.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector; /** * $Id$ diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStoreListenerSupport.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStoreListenerSupport.java new file mode 100644 index 00000000..47091b87 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/ObjectStoreListenerSupport.java @@ -0,0 +1,39 @@ +package org.ical4j.connector; + +import org.ical4j.connector.event.ListenerList; +import org.ical4j.connector.event.ObjectStoreEvent; +import org.ical4j.connector.event.ObjectStoreListener; + +/** + * Provide support for notifying collection listeners of events. + */ +public interface ObjectStoreListenerSupport { + + ListenerList> getObjectStoreListeners(); + + /** + * Register listener for collection events. + * @param listener a collection listener + */ + default void addObjectStoreListener(ObjectStoreListener listener) { + getObjectStoreListeners().add(listener); + } + + /** + * Unregister listener for collection events. + * @param listener a collection listener + */ + default void removeObjectStoreListener(ObjectStoreListener listener) { + getObjectStoreListeners().remove(listener); + } + + default void fireOnAddEvent(ObjectStore> source, ObjectCollection object) { + getObjectStoreListeners().getAll().forEach(listener -> listener.collectionAdded( + new ObjectStoreEvent(source, object))); + } + + default void fireOnRemoveEvent(ObjectStore> source, ObjectCollection object) { + getObjectStoreListeners().getAll().forEach(listener -> listener.collectionRemoved( + new ObjectStoreEvent<>(source, object))); + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ListenerList.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ListenerList.java new file mode 100644 index 00000000..5bc30500 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ListenerList.java @@ -0,0 +1,31 @@ +package org.ical4j.connector.event; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +public class ListenerList { + + private final List listeners; + + public ListenerList() { + this.listeners = new CopyOnWriteArrayList<>(); + } + + public boolean add(T listener) { + return listeners.add(listener); + } + + public boolean remove(T listener) { + return listeners.remove(listener); + } + + @SuppressWarnings("unchecked") + public List get(Class type) { + return (List) listeners.stream().filter(type::isInstance).collect(Collectors.toList()); + } + + public List getAll() { + return listeners; + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectCollectionEvent.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectCollectionEvent.java new file mode 100644 index 00000000..d2b8b3d6 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectCollectionEvent.java @@ -0,0 +1,19 @@ +package org.ical4j.connector.event; + +import org.ical4j.connector.ObjectCollection; + +import java.util.EventObject; + +public class ObjectCollectionEvent extends EventObject { + + private final T object; + + public ObjectCollectionEvent(ObjectCollection source, T object) { + super(source); + this.object = object; + } + + public T getObject() { + return object; + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectCollectionListener.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectCollectionListener.java new file mode 100644 index 00000000..7e32448c --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectCollectionListener.java @@ -0,0 +1,12 @@ +package org.ical4j.connector.event; + +public interface ObjectCollectionListener { + + void onAdd(ObjectCollectionEvent event); + + void onRemove(ObjectCollectionEvent event); + + void onMerge(ObjectCollectionEvent event); + + void onReplace(ObjectCollectionEvent event); +} diff --git a/src/main/java/net/fortuna/ical4j/connector/event/ObjectStoreEvent.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectStoreEvent.java similarity index 90% rename from src/main/java/net/fortuna/ical4j/connector/event/ObjectStoreEvent.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectStoreEvent.java index cb6c2de3..682f81a2 100755 --- a/src/main/java/net/fortuna/ical4j/connector/event/ObjectStoreEvent.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectStoreEvent.java @@ -29,12 +29,12 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.event; +package org.ical4j.connector.event; -import java.util.EventObject; +import org.ical4j.connector.ObjectCollection; +import org.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.ObjectCollection; -import net.fortuna.ical4j.connector.ObjectStore; +import java.util.EventObject; /** * @param the object collection type supported by the event source @@ -58,7 +58,7 @@ public class ObjectStoreEvent extends EventObject { * @param source the event source * @param collection the affected collection */ - public ObjectStoreEvent(ObjectStore> source, ObjectCollection collection) { + public ObjectStoreEvent(ObjectStore> source, ObjectCollection collection) { super(source); this.collection = collection; } diff --git a/src/main/java/net/fortuna/ical4j/connector/event/ObjectStoreListener.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectStoreListener.java similarity index 97% rename from src/main/java/net/fortuna/ical4j/connector/event/ObjectStoreListener.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectStoreListener.java index 224dca86..be0f8bc1 100755 --- a/src/main/java/net/fortuna/ical4j/connector/event/ObjectStoreListener.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/event/ObjectStoreListener.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.event; +package org.ical4j.connector.event; import java.util.EventListener; diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/local/AbstractLocalObjectCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/AbstractLocalObjectCollection.java new file mode 100644 index 00000000..3a7483d9 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/AbstractLocalObjectCollection.java @@ -0,0 +1,83 @@ +package org.ical4j.connector.local; + +import net.fortuna.ical4j.model.Calendar; +import org.ical4j.connector.AbstractObjectCollection; +import org.ical4j.connector.ObjectStoreException; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +abstract class AbstractLocalObjectCollection extends AbstractObjectCollection { + + private final File root; + + private final LocalCollectionConfiguration configuration; + + public AbstractLocalObjectCollection(File root) throws IOException { + this.root = Objects.requireNonNull(root); +// if (!root.isDirectory()) { +// throw new IllegalArgumentException("Root must be a directory"); +// } + var configRoot = new File(root, LocalCollectionConfiguration.DEFAULT_CONFIG_DIR); + if ((configRoot.exists() && !configRoot.isDirectory()) || + (!configRoot.exists() && !configRoot.mkdirs())) { + throw new IOException("Unable to initialise collection config"); + } + this.configuration = new LocalCollectionConfiguration(configRoot); + } + + protected File getRoot() { + return root; + } + + @Override + public String getDisplayName() { + return configuration.getDisplayName(); + } + + @Override + public String getDescription() { + return configuration.getDescription(); + } + + public String[] getSupportedComponentTypes() { + return configuration.getSupportedComponentTypes(); + } + + public Calendar getTimeZone() { + return configuration.getTimeZone(); + } + + public void setDisplayName(String displayName) throws IOException { + configuration.setDisplayName(displayName); + } + + public void setDescription(String description) throws IOException { + configuration.setDescription(description); + } + + public void setSupportedComponents(String[] supportedComponents) throws IOException { + configuration.setSupportedComponents(supportedComponents); + } + + public void setTimeZone(Calendar timezone) throws IOException { + configuration.setTimeZone(timezone); + } + + @Override + public void delete() throws ObjectStoreException { + if (Objects.requireNonNull( + root.list((root, name) -> !name.equals(LocalCollectionConfiguration.DEFAULT_CONFIG_DIR))).length > 0) { + throw new ObjectStoreException("Collection is not empty. Remove all contents before deleting."); + } + if (!configuration.delete() || !root.delete()) { + throw new ObjectStoreException("Unable to delete collection"); + } + } + + @Override + public String toString() { + return String.format("LocalCollection[%s]", getDisplayName()); + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/local/AbstractLocalObjectStore.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/AbstractLocalObjectStore.java new file mode 100644 index 00000000..1d71b1ba --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/AbstractLocalObjectStore.java @@ -0,0 +1,164 @@ +package org.ical4j.connector.local; + +import net.fortuna.ical4j.model.Calendar; +import org.ical4j.connector.AbstractObjectStore; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStoreException; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +abstract class AbstractLocalObjectStore> extends AbstractObjectStore { + + private final File root; + + AbstractLocalObjectStore(File root) { + this.root = Objects.requireNonNull(root); + if (root.exists() && !root.isDirectory()) { + throw new IllegalArgumentException("Root must be a directory"); + } else if (!root.exists() && !root.mkdirs()) { + throw new IllegalArgumentException("Unable to initialise root directory"); + } + } + + protected File getRoot() { + return root; + } + + @Override + public boolean connect() throws ObjectStoreException { + return false; + } + + @Override + public boolean connect(String username, char[] password) throws ObjectStoreException { + return false; + } + + @Override + public void disconnect() { + + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public C addCollection(String id) throws ObjectStoreException { + return addCollection(id, DEFAULT_WORKSPACE); + } + + @Override + public C addCollection(String id, String workspace) throws ObjectStoreException { + var collectionDir = new File(getWorkspaceDir(workspace), id); + if ((collectionDir.exists() && !collectionDir.isDirectory()) || + (!collectionDir.exists() && !collectionDir.mkdirs())) { + throw new ObjectStoreException("Unable to initialise collection"); + } + C collection = null; + try { + collection = getCollection(id); + } catch (ObjectNotFoundException e) { + try { + collection = newCollection(id, workspace); + } catch (IOException ex) { + throw new ObjectStoreException(ex); + } + } + + // notify listeners.. + fireOnAddEvent(this, collection); + + return collection; + } + + protected abstract C newCollection(String id, String workspace) throws IOException; + + @Override + public C addCollection(String id, String displayName, String description, String[] supportedComponents, Calendar timezone) throws ObjectStoreException { + return addCollection(id, displayName, description, supportedComponents, timezone, DEFAULT_WORKSPACE); + } + + @Override + public C addCollection(String id, String displayName, String description, String[] supportedComponents, + Calendar timezone, String workspace) throws ObjectStoreException { + C collection = addCollection(id, workspace); + try { + collection.setDisplayName(displayName); + collection.setDescription(description); + collection.setSupportedComponents(supportedComponents); + collection.setTimeZone(timezone); + } catch (IOException e) { + throw new ObjectStoreException(e); + } + + // notify listeners.. + fireOnAddEvent(this, collection); + + return collection; + } + + @Override + public C removeCollection(String id) throws ObjectNotFoundException, ObjectStoreException { + C collection = getCollection(id); + collection.delete(); + + // notify listeners.. + fireOnRemoveEvent(this, collection); + + return collection; + } + + @Override + public C getCollection(String id) throws ObjectStoreException, ObjectNotFoundException { + return getCollection(id, DEFAULT_WORKSPACE); + } + + @Override + public C getCollection(String id, String workspace) throws ObjectStoreException, ObjectNotFoundException { + var collectionDir = new File(getWorkspaceDir(workspace), id); + if (!collectionDir.exists() || !collectionDir.isDirectory()) { + throw new ObjectNotFoundException("Unable to retrieve collection"); + } + try { + return newCollection(id, workspace); + } catch (IOException e) { + throw new ObjectStoreException(e); + } + } + + @Override + public List getCollections() { + return getCollections(DEFAULT_WORKSPACE); + } + + @Override + public List getCollections(String workspace) { + return Arrays.stream(Objects.requireNonNull(getWorkspaceDir(workspace).list())).map(name -> { + try { + return newCollection(name, workspace); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } + + protected File getWorkspaceDir(String workspace) { + var workspaceDir = new File(root, workspace); + if (!workspaceDir.exists() && !workspaceDir.mkdir()) { + throw new IllegalArgumentException("Invalid workspace"); + } + return workspaceDir; + } + + @Override + public List listWorkspaceIds() { + return Arrays.asList(Objects.requireNonNull(root.list())); + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCalendarCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCalendarCollection.java new file mode 100644 index 00000000..31b956bf --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCalendarCollection.java @@ -0,0 +1,187 @@ +package org.ical4j.connector.local; + +import net.fortuna.ical4j.data.CalendarOutputter; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.property.Uid; +import net.fortuna.ical4j.util.Calendars; +import org.ical4j.connector.CalendarCollection; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.MediaType; +import org.ical4j.connector.ObjectStoreException; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class LocalCalendarCollection extends AbstractLocalObjectCollection implements CalendarCollection { + + private static final MediaType[] SUPPORTED_MEDIA_TYPES = new MediaType[1]; + static { + SUPPORTED_MEDIA_TYPES[0] = MediaType.ICALENDAR_2_0; + } + + private static final String FILE_EXTENSION = ".ics"; + + public LocalCalendarCollection(File root) throws IOException { + super(root); +// setDisplayName(root.getName()); +// setSupportedComponents(new String[]{"VEVENT", "VTODO", "VJOURNAL"}); + } + + @Override + public MediaType[] getSupportedMediaTypes() { + return SUPPORTED_MEDIA_TYPES; + } + + @Override + public long getMaxResourceSize() { + return 0; + } + + @Override + public Instant getMinDateTime() { + return null; + } + + @Override + public Instant getMaxDateTime() { + return null; + } + + @Override + public Integer getMaxInstances() { + return null; + } + + @Override + public Integer getMaxAttendeesPerInstance() { + return null; + } + + @Override + public List listObjectUIDs() { + return Arrays.stream(getObjectFiles()).map(file -> file.getName().split(FILE_EXTENSION)[0]) + .collect(Collectors.toList()); + } + + @Override + public String add(Calendar object) throws ObjectStoreException { + var uid = object.getUid(); + Optional existing = get(uid.getValue()); + + try (var writer = new FileWriter(new File(getRoot(), uid.getValue() + FILE_EXTENSION))) { + if (existing.isPresent()) { + new CalendarOutputter(false).output(existing.get().merge(object), writer); + } else { + new CalendarOutputter(false).output(object, writer); + } + } catch (IOException e) { + throw new ObjectStoreException("Error writing calendar file", e); + } + + // notify listeners.. + fireOnAddEvent(this, object); + + return uid.getValue(); + } + + @Override + public Optional get(String uid) { + var calendarFile = new File(getRoot(), uid + FILE_EXTENSION); + if (!calendarFile.exists()) { + return Optional.empty(); + } + + try { + return Optional.of(Calendars.load(calendarFile.getAbsolutePath())); + } catch (IOException | ParserException e) { + throw new RuntimeException(e); + } + } + + @Override + public List removeAll(String... uid) throws FailedOperationException { + List removed = new ArrayList<>(); + for (var u : uid) { + var calendarFile = new File(getRoot(), u + FILE_EXTENSION); + if (calendarFile.exists()) { + Optional cal = get(u); + if (cal.isPresent()) { + if (!calendarFile.delete()) { + throw new FailedOperationException("Unable to delete calendar: " + u); + } + removed.add(cal.get()); + fireOnRemoveEvent(this, cal.get()); + } + } + } + return removed; + } + + @Override + public Uid[] merge(Calendar calendar) throws ObjectStoreException { + var uidCalendars = calendar.split(); + for (var c : uidCalendars) { + var uid = c.getUid(); + Optional existing = get(uid.getValue()); + + if (existing.isPresent()) { + // TODO: potentially merge/replace existing.. + throw new ObjectStoreException("Calendar already exists"); + } + + try (var writer = new FileWriter(new File(getRoot(), uid.getValue() + FILE_EXTENSION))) { + new CalendarOutputter(false).output(c, writer); + } catch (IOException e) { + throw new ObjectStoreException("Error writing calendar file", e); + } + } + + // notify listeners.. + fireOnMergeEvent(this, calendar); + + return Arrays.stream(uidCalendars).map(Calendar::getUid).toArray(Uid[]::new); + } + + @Override + public Calendar export() { + var export = new Calendar(); + for (var object : getObjectFiles()) { + try { + export = export.merge(Calendars.load(object.getAbsolutePath())); + } catch (IOException | ParserException e) { + throw new RuntimeException(e); + } + } + return export; + } + +// @Override +// public Iterable getAll() throws ObjectStoreException { +// List calendars = new ArrayList<>(); +// +// File[] componentFiles = getObjectFiles(); +// if (componentFiles != null) { +// try { +// for (File file : componentFiles) { +// calendars.add(Calendars.load(file.getAbsolutePath())); +// } +// } catch (IOException | ParserException e) { +// throw new ObjectStoreException(e); +// } +// } +// return calendars; +// } + + private File[] getObjectFiles() { + return getRoot().listFiles(pathname -> + !pathname.isDirectory() && pathname.getName().endsWith(FILE_EXTENSION)); + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCalendarStore.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCalendarStore.java new file mode 100644 index 00000000..3a0acd11 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCalendarStore.java @@ -0,0 +1,20 @@ +package org.ical4j.connector.local; + +import net.fortuna.ical4j.model.Calendar; +import org.ical4j.connector.ObjectStore; + +import java.io.File; +import java.io.IOException; + +public class LocalCalendarStore extends AbstractLocalObjectStore + implements ObjectStore { + + public LocalCalendarStore(File root) { + super(root); + } + + @Override + protected LocalCalendarCollection newCollection(String id, String workspace) throws IOException { + return new LocalCalendarCollection(new File(getWorkspaceDir(workspace), id)); + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCardCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCardCollection.java new file mode 100644 index 00000000..a57a10c0 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCardCollection.java @@ -0,0 +1,167 @@ +package org.ical4j.connector.local; + +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.ConstraintViolationException; +import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.VCardBuilder; +import net.fortuna.ical4j.vcard.VCardOutputter; +import net.fortuna.ical4j.vcard.property.Uid; +import org.ical4j.connector.CardCollection; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.MediaType; +import org.ical4j.connector.ObjectStoreException; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class LocalCardCollection extends AbstractLocalObjectCollection implements CardCollection { + + private static final MediaType[] SUPPORTED_MEDIA_TYPES = new MediaType[1]; + static { + SUPPORTED_MEDIA_TYPES[0] = MediaType.VCARD_4_0; + } + + private static final String FILE_EXTENSION = ".vcf"; + + public LocalCardCollection(File root) throws IOException { + super(root); + } + + @Override + public List listObjectUIDs() { + return Arrays.stream(getObjectFiles()).map(file -> file.getName().split(FILE_EXTENSION)[0]) + .collect(Collectors.toList()); + } + + @Override + public String add(VCard card) throws ObjectStoreException, ConstraintViolationException { + var uid = card.getUid(); + Optional existing = get(uid.getValue()); + + try (var writer = new FileWriter(new File(getRoot(), uid.getValue() + FILE_EXTENSION))) { + if (existing.isPresent()) { + new VCardOutputter(false).output(existing.get().merge(card), writer); + } else { + new VCardOutputter(false).output(card, writer); + } + } catch (IOException e) { + throw new ObjectStoreException("Error writing card file", e); + } + + // notify listeners.. + fireOnAddEvent(this, card); + + return uid.getValue(); + } + + @Override + public Optional get(String uid) { + var cardFile = new File(getRoot(), uid + FILE_EXTENSION); + if (!cardFile.exists()) { + return Optional.empty(); + } + + try { + return Optional.of(new VCardBuilder(Files.newInputStream(cardFile.toPath())).build()); + } catch (IOException | ParserException e) { + throw new RuntimeException(e); + } + } + + @Override + public List removeAll(String... uid) throws FailedOperationException { + List removed = new ArrayList<>(); + for (var u : uid) { + var cardFile = new File(getRoot(), u + FILE_EXTENSION); + if (cardFile.exists()) { + Optional card = get(u); + if (card.isPresent()) { + if (!cardFile.delete()) { + throw new FailedOperationException("Unable to delete card: " + u); + } + removed.add(card.get()); + fireOnRemoveEvent(this, card.get()); + } + } + } + return removed; + } + + @Override + public Uid[] merge(VCard card) throws ObjectStoreException, ConstraintViolationException { + var uidCards = card.split(); + for (var c : uidCards) { + var uid = c.getUid(); + Optional existing = get(uid.getValue()); + + if (existing.isPresent()) { + // TODO: potentially merge/replace existing.. + throw new ObjectStoreException("Card already exists"); + } + + try (var writer = new FileWriter(new File(getRoot(), uid.getValue() + FILE_EXTENSION))) { + new VCardOutputter(false).output(c, writer); + } catch (IOException e) { + throw new ObjectStoreException("Error writing card file", e); + } + } + + // notify listeners.. + fireOnMergeEvent(this, card); + + return Arrays.stream(uidCards).map(VCard::getUid).toArray(Uid[]::new); + } + + private void save(VCard card) throws ObjectStoreException { + var uid = card.getUid(); + + try (var writer = new FileWriter(new File(getRoot(), uid.getValue() + FILE_EXTENSION))) { + new VCardOutputter(false).output(card, writer); + } catch (IOException e) { + throw new ObjectStoreException("Error writing card file", e); + } + } + + @Override + public VCard export() { + var export = new VCard(); + for (var object : getObjectFiles()) { + try { + export = export.merge(new VCardBuilder(Files.newInputStream(object.toPath())).build()); + } catch (IOException | ParserException e) { + throw new RuntimeException(e); + } + } + return export; + } + +// @Override +// public Iterable getAll() throws ObjectStoreException { +// List cards = new ArrayList<>(); +// +// File[] componentFiles = getObjectFiles(); +// if (componentFiles != null) { +// try { +// for (File file : componentFiles) { +// VCardBuilder builder = new VCardBuilder(Files.newInputStream(file.toPath())); +// cards.add(builder.build()); +// } +// } catch (IOException | ParserException e) { +// throw new ObjectStoreException(e); +// } +// } +// return cards; +// } + + private File[] getObjectFiles() { + return getRoot().listFiles(pathname -> + !pathname.isDirectory() && pathname.getName().endsWith(FILE_EXTENSION)); + } +} diff --git a/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCardStore.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCardStore.java new file mode 100644 index 00000000..1b5cc4d7 --- /dev/null +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCardStore.java @@ -0,0 +1,20 @@ +package org.ical4j.connector.local; + +import net.fortuna.ical4j.vcard.VCard; +import org.ical4j.connector.ObjectStore; + +import java.io.File; +import java.io.IOException; + +public class LocalCardStore extends AbstractLocalObjectStore + implements ObjectStore { + + public LocalCardStore(File root) { + super(root); + } + + @Override + protected LocalCardCollection newCollection(String id, String workspace) throws IOException { + return new LocalCardCollection(new File(getWorkspaceDir(workspace), id)); + } +} diff --git a/src/main/java/net/fortuna/ical4j/connector/local/AbstractLocalObjectCollection.java b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCollectionConfiguration.java similarity index 63% rename from src/main/java/net/fortuna/ical4j/connector/local/AbstractLocalObjectCollection.java rename to ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCollectionConfiguration.java index d349230e..75a51e9f 100644 --- a/src/main/java/net/fortuna/ical4j/connector/local/AbstractLocalObjectCollection.java +++ b/ical4j-connector-api/src/main/java/org/ical4j/connector/local/LocalCollectionConfiguration.java @@ -1,11 +1,9 @@ -package net.fortuna.ical4j.connector.local; +package org.ical4j.connector.local; -import net.fortuna.ical4j.connector.ObjectCollection; import net.fortuna.ical4j.data.CalendarOutputter; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.util.Calendars; -import net.fortuna.ical4j.util.ResourceLoader; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,51 +11,51 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.io.InputStream; +import java.nio.file.Files; import java.util.Date; import java.util.Properties; -public abstract class AbstractLocalObjectCollection implements ObjectCollection { +class LocalCollectionConfiguration { - private static final Logger LOG = LoggerFactory.getLogger(AbstractLocalObjectCollection.class); + public static final String DEFAULT_CONFIG_DIR = ".ical4j"; - private static final String PROPERTIES_FILE_NAME = ".config"; + private static final String PROPERTIES_FILE_NAME = "config"; - private static final String TIMEZONE_FILE_NAME = ".timezone"; + private static final String TIMEZONE_FILE_NAME = "timezone"; + + private static final Logger LOG = LoggerFactory.getLogger(LocalCollectionConfiguration.class); private final File root; private final Properties properties; - public AbstractLocalObjectCollection(File root) { + public LocalCollectionConfiguration() { + this(new File(DEFAULT_CONFIG_DIR)); + } + + public LocalCollectionConfiguration(File root) { if (!root.isDirectory()) { throw new IllegalArgumentException("Root must be a directory"); } this.root = root; this.properties = new Properties(); - try (InputStream in = ResourceLoader.getResourceAsStream(PROPERTIES_FILE_NAME)) { + try (var in = Files.newInputStream(new File(root, PROPERTIES_FILE_NAME).toPath())) { properties.load(in); } catch (IOException | NullPointerException e) { - LOG.info("ical4j.properties not found."); + LOG.info("ical4j config not found."); } } - protected File getRoot() { - return root; - } - - @Override public String getDisplayName() { return properties.getProperty("DisplayName"); } - @Override public String getDescription() { return properties.getProperty("Description"); } public String[] getSupportedComponentTypes() { - return properties.getProperty("SupportedComponents").split(","); + return properties.getProperty("SupportedComponents", "").split(","); } public Calendar getTimeZone() { @@ -88,8 +86,18 @@ public void setTimeZone(Calendar timezone) throws IOException { new CalendarOutputter(false).output(timezone, new FileWriter(new File(root, TIMEZONE_FILE_NAME))); } - private void saveProperties() throws IOException { - properties.store(new FileWriter(new File(root, PROPERTIES_FILE_NAME)), String.format("%s", new Date())); + public boolean delete() { + new File(root, PROPERTIES_FILE_NAME).delete(); + new File(root, TIMEZONE_FILE_NAME).delete(); + return root.delete(); } + private void saveProperties() throws IOException { +// if (!root.exists() && !root.mkdirs()) { +// throw new IOException("Unable to create config directory"); +// } + try (var fw = new FileWriter(new File(root, PROPERTIES_FILE_NAME))) { + properties.store(fw, String.format("%s", new Date())); + } + } } diff --git a/src/main/resources/overview.html b/ical4j-connector-api/src/main/resources/overview.html similarity index 100% rename from src/main/resources/overview.html rename to ical4j-connector-api/src/main/resources/overview.html diff --git a/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/AbstractLocalTest.groovy b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/AbstractLocalTest.groovy new file mode 100644 index 00000000..78db21ba --- /dev/null +++ b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/AbstractLocalTest.groovy @@ -0,0 +1,19 @@ +package org.ical4j.connector.local + +import spock.lang.Shared +import spock.lang.Specification + +abstract class AbstractLocalTest extends Specification { + + @Shared + File storeLocation, workspaceLocation + + def setup() { + storeLocation = ['build', getClass().name] + workspaceLocation = [storeLocation, 'default'] + } + + def cleanup() { + storeLocation.deleteDir() + } +} diff --git a/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCalendarCollectionTest.groovy b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCalendarCollectionTest.groovy new file mode 100644 index 00000000..4c405269 --- /dev/null +++ b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCalendarCollectionTest.groovy @@ -0,0 +1,115 @@ +package org.ical4j.connector.local + +import net.fortuna.ical4j.model.Calendar +import net.fortuna.ical4j.model.Component +import net.fortuna.ical4j.model.ContentBuilder +import net.fortuna.ical4j.model.Property +import net.fortuna.ical4j.util.RandomUidGenerator +import org.ical4j.connector.event.ObjectCollectionEvent +import org.ical4j.connector.event.ObjectCollectionListener +import spock.lang.Shared + +class LocalCalendarCollectionTest extends AbstractLocalTest { + + @Shared + Calendar calendar + + def setupSpec() { + calendar = new ContentBuilder().calendar { + prodid '-//Ben Fortuna//iCal4j 1.0//EN' + version '2.0' + vevent { + uid new RandomUidGenerator().generateUid() + dtstamp() + dtstart('20090810', parameters: parameters { value 'DATE' }) + action 'DISPLAY' + attach('http://example.com/attachment', parameters: parameters() { value 'URI' }) + } + } + } + + def 'test add calendar to collection'() { + given: 'a local calendar collection' + LocalCalendarStore calendarStore = [storeLocation] + def collection = calendarStore.addCollection('public_holidays') + + and: 'a collection listener' + ObjectCollectionEvent event + ObjectCollectionListener listener = { event = it } as ObjectCollectionListener + collection.addObjectCollectionListener(listener) + + when: 'the new calendar is added to the collection' + collection.add(calendar) + + then: 'a new calendar file is created' + new File(workspaceLocation, + "public_holidays/${calendar.getComponent(Component.VEVENT).get().getRequiredProperty(Property.UID).getValue()}.ics").exists() + + and: 'the listener is notified' + event != null && event.object == calendar + } + + def 'test remove calendar from collection'() { + given: 'a local calendar collection' + LocalCalendarStore calendarStore = [storeLocation] + def collection = calendarStore.addCollection('public_holidays') + + and: 'a calendar object that is added to the collection' + collection.add(calendar) + + when: 'the calendar is removed from the collection' + def removed = collection.removeAll(calendar.getUid().value) + + then: 'the exsiting calendar file is deleted' + !new File(workspaceLocation, + "public_holidays/${calendar.getComponent(Component.VEVENT).get().getRequiredProperty(Property.UID).getValue()}.ics").exists() + + and: 'removed calendar is identical to added' + removed.contains(calendar) + } + + def 'test get calendar from collection'() { + given: 'a local calendar collection' + LocalCalendarStore calendarStore = [storeLocation] + def collection = calendarStore.addCollection('public_holidays') + + and: 'a calendar object that is added to the collection' + collection.add(calendar) + + when: 'the calendar is retrieved from the collection' + def retrieved = collection.get(calendar.getUid().value) + + then: 'retrieved calendar is identical to added' + retrieved == Optional.of(calendar) + } + + def 'test list object uids in collection'() { + given: 'a local calendar collection' + LocalCalendarStore calendarStore = [storeLocation] + def collection = calendarStore.addCollection('public_holidays') + + and: 'a calendar object that is added to the collection' + collection.add(calendar) + + when: 'the collection object uids are listed' + def uids = collection.listObjectUIDs() + + then: 'the added calendar uid is in the list' + uids.contains(calendar.getUid().value) + } + + def 'test export collection'() { + given: 'a local calendar collection' + LocalCalendarStore calendarStore = [storeLocation] + def collection = calendarStore.addCollection('public_holidays') + + and: 'a calendar object that is added to the collection' + collection.add(calendar) + + when: 'the collection is exported' + def export = collection.export() + + then: 'the exported collection is identical to added' + export == calendar + } +} diff --git a/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCalendarStoreTest.groovy b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCalendarStoreTest.groovy new file mode 100644 index 00000000..7b6be9a2 --- /dev/null +++ b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCalendarStoreTest.groovy @@ -0,0 +1,90 @@ +package org.ical4j.connector.local + +import net.fortuna.ical4j.model.Calendar +import net.fortuna.ical4j.model.Component +import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory +import net.fortuna.ical4j.util.Calendars +import org.ical4j.connector.event.ObjectStoreEvent +import org.ical4j.connector.event.ObjectStoreListener + +class LocalCalendarStoreTest extends AbstractLocalTest { + + def 'test create collection'() { + given: 'a new local calendar store' + LocalCalendarStore calendarStore = [storeLocation] + + and: 'a store listener' + ObjectStoreEvent event + ObjectStoreListener listener = { event = it } as ObjectStoreListener + calendarStore.addObjectStoreListener(listener) + + when: 'a new collection is added' + LocalCalendarCollection collection = calendarStore.addCollection('public_holidays') + + then: 'a local collection directory is added' + new File(workspaceLocation, 'public_holidays').exists() + + and: 'the listener is notified' + event != null && event.collection == collection + } + + def 'test create and initialise collection'() { + given: 'a new local calendar store' + LocalCalendarStore calendarStore = [storeLocation] + + and: 'a timezone calendar' + def timezone = Calendars.wrap(new DefaultTimeZoneRegistryFactory().createRegistry() + .getTimeZone('Australia/Melbourne').getVTimeZone()) + + when: 'a new collection is added' + def collection = calendarStore.addCollection('public_holidays', 'Public Holidays', + 'Victorian public holidays', [Component.VEVENT] as String[], timezone) + + then: 'a local collection directory is added' + new File(workspaceLocation, 'public_holidays').exists() + + and: 'the collection properties are saved' + collection.displayName == 'Public Holidays' + collection.description == 'Victorian public holidays' + collection.supportedComponentTypes == [Component.VEVENT] as String[] + collection.timeZone == timezone + + } + + def 'test get collection'() { + given: 'a new local calendar store' + LocalCalendarStore calendarStore = [storeLocation] + + and: 'a timezone calendar' + def timezone = Calendars.wrap(new DefaultTimeZoneRegistryFactory().createRegistry() + .getTimeZone('Australia/Melbourne').getVTimeZone()) + + and: 'a new collection is added' + calendarStore.addCollection('public_holidays', 'Public Holidays', + 'Victorian public holidays', [Component.VEVENT] as String[], timezone) + + when: 'collection is retrieved' + def collection = calendarStore.getCollection('public_holidays') + + then: 'the collection properties are saved' + collection.displayName == 'Public Holidays' + collection.description == 'Victorian public holidays' + collection.supportedComponentTypes == [Component.VEVENT] as String[] + collection.timeZone == timezone + + } + + def 'test remove collection'() { + given: 'a new local calendar store' + LocalCalendarStore calendarStore = [storeLocation] + + when: 'a new collection is added' + def c = calendarStore.addCollection('public_holidays') + + and: 'the collection is removed' + c.delete() + + then: 'a local collection directory is deleted' + !new File(storeLocation, 'public_holidays').exists() + } +} diff --git a/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCardCollectionTest.groovy b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCardCollectionTest.groovy new file mode 100644 index 00000000..37dd2f84 --- /dev/null +++ b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCardCollectionTest.groovy @@ -0,0 +1,110 @@ +package org.ical4j.connector.local + +import net.fortuna.ical4j.vcard.ContentBuilder +import net.fortuna.ical4j.vcard.VCard +import org.ical4j.connector.event.ObjectCollectionEvent +import org.ical4j.connector.event.ObjectCollectionListener +import spock.lang.Shared + +class LocalCardCollectionTest extends AbstractLocalTest { + + @Shared + VCard card + + def setupSpec() { + card = new ContentBuilder().vcard() { + entity { + version '4.0' + uid UUID.randomUUID().toString() + fn 'test' + n('example') { + value 'text' + } + photo(value: 'http://example.com', parameters: [value('uri')]) + } + } + } + + def 'test add card to collection'() { + given: 'a local card collection' + LocalCardStore cardStore = [storeLocation] + def collection = cardStore.addCollection('contacts') + + and: 'a collection listener' + ObjectCollectionEvent event + ObjectCollectionListener listener = { event = it } as ObjectCollectionListener + collection.addObjectCollectionListener(listener) + + when: 'the new card is added to the collection' + collection.add(card) + + then: 'a new card file is created' + new File(workspaceLocation, "contacts/${card.uid.value}.vcf").exists() + + and: 'the listener is notified' + event != null && event.object == card + } + + def 'test remove card from collection'() { + given: 'a local card collection' + LocalCardStore cardStore = [storeLocation] + def collection = cardStore.addCollection('contacts') + + and: 'a card object added' + collection.add(card) + + when: 'the card is removed' + def removed = collection.removeAll(card.uid.value) + + then: 'the existing card file is deleted' + !new File(storeLocation, "contacts/${card.uid.getValue()}.vcf").exists() + + and: 'removed card is identical to added' + removed.contains(card) + } + + def 'test get card from collection'() { + given: 'a local card collection' + LocalCardStore cardStore = [storeLocation] + def collection = cardStore.addCollection('contacts') + + and: 'a card object added' + collection.add(card) + + when: 'the card is removed' + def retrieved = collection.get(card.uid.value) + + then: 'retrieved card is identical to added' + retrieved == Optional.of(card) + } + + def 'test list object uids in collection'() { + given: 'a local card collection' + LocalCardStore cardStore = [storeLocation] + def collection = cardStore.addCollection('contacts') + + and: 'a card object that is added to the collection' + collection.add(card) + + when: 'the collection object uids are listed' + def uids = collection.listObjectUIDs() + + then: 'the added calendar uid is in the list' + uids.contains(card.uid.value) + } + + def 'test export collection'() { + given: 'a local card collection' + LocalCardStore cardStore = [storeLocation] + def collection = cardStore.addCollection('contacts') + + and: 'a card object that is added to the collection' + collection.add(card) + + when: 'the collection is exported' + def export = collection.export() + + then: 'the exported collection is identical to added' + export == card + } +} diff --git a/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCardStoreTest.groovy b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCardStoreTest.groovy new file mode 100644 index 00000000..b4f2403b --- /dev/null +++ b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCardStoreTest.groovy @@ -0,0 +1,52 @@ +package org.ical4j.connector.local + + +import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory +import net.fortuna.ical4j.util.Calendars +import net.fortuna.ical4j.vcard.VCard +import org.ical4j.connector.event.ObjectStoreEvent +import org.ical4j.connector.event.ObjectStoreListener + +class LocalCardStoreTest extends AbstractLocalTest { + + def 'test create collection'() { + given: 'a new local card store' + LocalCardStore cardStore = [storeLocation] + + and: 'a store listener' + ObjectStoreEvent event + ObjectStoreListener listener = { event = it } as ObjectStoreListener + cardStore.addObjectStoreListener(listener) + + when: 'a new collection is added' + def collection = cardStore.addCollection('contacts') + + then: 'a local collection directory is added' + new File(workspaceLocation, 'contacts').exists() + + and: 'the listener is notified' + event != null && event.collection == collection + } + + def 'test create and initialise collection'() { + given: 'a new local card store' + LocalCardStore cardStore = [storeLocation] + + and: 'a timezone card' + def timezone = Calendars.wrap(new DefaultTimeZoneRegistryFactory().createRegistry() + .getTimeZone('Australia/Melbourne').getVTimeZone()) + + when: 'a new collection is added' + def collection = cardStore.addCollection('contacts', 'Contacts', + 'Personal Contacts', ['VCARD'] as String[], timezone) + + then: 'a local collection directory is added' + new File(workspaceLocation, 'contacts').exists() + + and: 'the collection properties are saved' + collection.displayName == 'Contacts' + collection.description == 'Personal Contacts' + collection.supportedComponentTypes == ['VCARD'] as String[] + collection.timeZone == timezone + } +} diff --git a/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCollectionConfigurationTest.groovy b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCollectionConfigurationTest.groovy new file mode 100644 index 00000000..8ee6a3bc --- /dev/null +++ b/ical4j-connector-api/src/test/groovy/org/ical4j/connector/local/LocalCollectionConfigurationTest.groovy @@ -0,0 +1,23 @@ +package org.ical4j.connector.local + +import spock.lang.Specification + +class LocalCollectionConfigurationTest extends Specification { + + def 'test set display name'() { + given: 'a temp config directory' + def configDir = File.createTempDir() + + and: 'a local configuration' + LocalCollectionConfiguration configuration = [configDir] + + when: 'display name is set' + configuration.displayName = 'Test Display Name' + + then: 'display name is persisted in memory' + configuration.displayName == 'Test Display Name' + + and: 'and on disk' + new LocalCollectionConfiguration(configDir).displayName == 'Test Display Name' + } +} diff --git a/src/test/resources/ical4j.properties b/ical4j-connector-api/src/test/resources/ical4j.properties similarity index 95% rename from src/test/resources/ical4j.properties rename to ical4j-connector-api/src/test/resources/ical4j.properties index a3fa1421..d8ca4e74 100644 --- a/src/test/resources/ical4j.properties +++ b/ical4j-connector-api/src/test/resources/ical4j.properties @@ -37,3 +37,5 @@ ical4j.parsing.relaxed=true #ical4j.connector.dav.preemptiveauth={true|false} net.fortuna.ical4j.timezone.cache.impl=net.fortuna.ical4j.util.MapTimeZoneCache + +ical4j.connector.store.class=org.ical4j.connector.local.LocalCalendarStore diff --git a/src/test/resources/logback.groovy b/ical4j-connector-api/src/test/resources/logback.groovy similarity index 100% rename from src/test/resources/logback.groovy rename to ical4j-connector-api/src/test/resources/logback.groovy diff --git a/ical4j-connector-dav/README.md b/ical4j-connector-dav/README.md new file mode 100644 index 00000000..657015e9 --- /dev/null +++ b/ical4j-connector-dav/README.md @@ -0,0 +1,167 @@ +[Jackrabbit WebDAV]: https://jackrabbit.apache.org/jcr/components/jackrabbit-webdav-library.html +[DAVResource]: https://jackrabbit.apache.org/api/trunk/org/apache/jackrabbit/webdav/DavResource.html + +# iCal4j Connector for CalDAV and CardDAV + +iCalendar and vCard data management with CalDAV and CardDAV. + +## Overview + +This library provides client support for backend calendar services using the iCal4j object model. The +most popular services implement DAV extensions for Calendaring (CalDAV) and VCard (CardDAV). + +## CalDAV and CardDAV + +The iCal4j Connector now supports three approaches for integrating with CalDAV and CardDAV servers. First +there is a low-level DAV client that supports HTTP methods for communicating with WebDAV servers. This is +defined by the CalDAVSupport and CardDAVSupport interfaces. The +[DAVResource] implementation that builds on the [Jackrabbit WebDAV] library supports response caching and path +abstraction for different +server implementations. Finally the CalendarStore, CardStore, CalendarCollection and CardCollection interfaces provide +a higher-level abstraction for +Calendar and VCard resource management including collection discovery. + +### DAV Client + +The DAV client may be used to perform explicit CalDAV and CardDAV operations against a specified URL. A +DAV client instance is initialised with a server URL and configuration options. + +```java +DavClientConfiguration configuration = new DavClientConfiguration().withPreemptiveAuth(true) + .withFollowRedirects(true); +DavClientFactory clientFactory = new DavClientFactory(configuration); + +DavClient client = clientFactory.newInstance("https://dav.example.com"); +``` + +Note that whilst the `DavClientFactory` currently returns a concrete type, it is recommended to use +interfaces for local references. + +```java +CalDavSupport caldav = clientFactory.newInstance("https://dav.example.com/.well-known/caldav"); + +CardDavSupport carddav = clientFactory.newInstance("https://dav.example.com/.well-known/carddav"); +``` + +#### Authentication + +TBD. + +#### Methods + +New calendar collections are created via the `MKCALENDAR` method. + +```java +DavPropertySet props = new DavPropertySet(); +props.add(new DavPropertyBuilder<>().name(DavPropertyName.DISPLAYNAME).value('Test Collection').build()); +props.add(new DavPropertyBuilder<>().name(CalDavPropertyName.CALENDAR_DESCRIPTION) + .value('A simple mkcalendar test').build()); +caldav.mkCalendar('admin/test', props); +``` + +Existing calendar resources are retrieved via the `GET` method. + +```java +Calendar calendar = caldav.getCalendar('admin/test'); +``` + +Resource and collection metadata is accessed via the `PROPFIND` method. + +```java +DavPropertySet props = caldav.propFind('admin', SecurityConstants.PRINCIPAL_COLLECTION_SET); +``` + +Note that to support multiple CalDAV implementations you may need to use a PathResolver. + +```java +String path = PathResolver.Defaults.RADICALE.getCalendarPath("test", "admin"); +Calendar calendar = caldav.getCalendar(path); +``` + +### DavResource + +A `DavResource` is a higher-level abstraction of a resource path that supports hierarchical discovery of +resources and caching of properties. + +```java +DavClientConfiguration configuration = new DavClientConfiguration().withPreemptiveAuth(true) + .withFollowRedirects(true); +DavClientFactory clientFactory = new DavClientFactory(configuration); + +DavLocatorFactory locatorFactory = new CalDavLocatorFactory(PathResolver.Defaults.RADICALE); +DavResourceLocator locator = locatorFactory.createResourceLocator("https://dav.example.com", + "user", "testcal"); + +DavResourceFactory resourceFactory = new CalDavResourceFactory(clientFactory); +DavResource resource = resourceFactory.createResource(locator, null); +``` + +A `DavResource` may be queried for common metadata properties. + +```java +if (resource.isCollection()) { + System.out.println("Collection name:" + resource.getDisplayName()); + System.out.println("Collection exists:" + resource.exists()); + System.out.println("Collection description:" + + resource.getProperty(CalDavPropertyName.CALENDAR_DESCRIPTION).getValue()); +} +``` + +A `DavResource` may be altered by adding and removing properties. + +```java +// overwrite resource description property +resource.setProperty(new DavPropertyBuilder<>().name(CalDavPropertyName.CALENDAR_DESCRIPTION) + .value('A simple mkcalendar test').build()); + +// unset calendar timezone +resource.removeProperty(CalDavPropertyName.CALENDAR_TIMEZONE); + +// add and remove properties with a single request +resource.alterProperties(Arrays.asList( + new DavPropertyBuilder<>().name(CalDavPropertyName.CALENDAR_DESCRIPTION) + .value('A simple mkcalendar test').build(), + CalDavPropertyName.CALENDAR_TIMEZONE +)); +``` + +Child and sibling resources are also easily accessed. + +```java +// add a child resource +resource.addMember(...); + +// add a sibling resource +resource.getCollection().addMember(...); + +// remove all children +for (DavResource child : resouce.getMembers()) { + resource.removeMember(child); +} +``` + +### CalDavCalendarStore and CardDavStore + +The third method, with the highest level of abstraction are the CalDAV and CardDAV implementations of +`CalendarStore` and `CardStore`. This approach provides a logical separation of collections and other +resources, as well as supporting more complex querying. + +```java +CalendarStore store = new CalDavCalendarStore(...); +store.connect('admin', 'admin'.toCharArray()); + +for (CalendarCollection collection : store.getCollections()) { + System.out.println("Collection name: " + collection.getDisplayName()); +} +``` + +Collections are accessed via a store instance. + +```java +CalendarCollection collection = store.getCollection("testcol"); +System.out.println("Collection description: " + collection.getDescription()); +System.out.println("Collection timezone: " + collection.getTimeZone()); + +// retrieve a calendar resource filtered on UID +Calendar cal = collection.getCalendar(...); +``` diff --git a/ical4j-connector-dav/build.gradle b/ical4j-connector-dav/build.gradle new file mode 100644 index 00000000..c7694226 --- /dev/null +++ b/ical4j-connector-dav/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'org.gradlex.extra-java-module-info' version '1.9' +} + +extraJavaModuleInfo { + deriveAutomaticModuleNamesFromFileNames.set(true) +} + +dependencies { + api project(':ical4j-connector-api'), + "org.jetbrains:annotations:$jetbrainsVersion" + + api("org.apache.httpcomponents:httpclient:$httpclientVersion") { + exclude(group: 'commons-logging', module: 'commons-logging') + } + + api("org.apache.jackrabbit:jackrabbit-webdav:$jackrabbitWebdavVersion") { + exclude(group: 'org.apache.httpcomponents', module: 'httpclient') + } + implementation 'javax.servlet:servlet-api:2.5', 'org.hamcrest:hamcrest:2.2' +} diff --git a/ical4j-connector-dav/src/main/java/module-info.java b/ical4j-connector-dav/src/main/java/module-info.java new file mode 100644 index 00000000..335a434d --- /dev/null +++ b/ical4j-connector-dav/src/main/java/module-info.java @@ -0,0 +1,21 @@ +module ical4j.connector.dav { + requires java.base; + requires java.xml; + + requires transitive ical4j.connector.api; + + requires org.apache.httpcomponents.httpcore; + requires org.apache.httpcomponents.httpclient; + requires jackrabbit.webdav; + + requires transitive org.slf4j; + requires static transitive org.jetbrains.annotations; + requires static org.hamcrest; + requires org.apache.commons.io; + + exports org.ical4j.connector.dav; + exports org.ical4j.connector.dav.method; + exports org.ical4j.connector.dav.property; + exports org.ical4j.connector.dav.request; + exports org.ical4j.connector.dav.response; +} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/AbstractDavObjectCollection.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavObjectCollection.java similarity index 68% rename from src/main/java/net/fortuna/ical4j/connector/dav/AbstractDavObjectCollection.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavObjectCollection.java index 98309202..6bb32ef9 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/AbstractDavObjectCollection.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavObjectCollection.java @@ -29,26 +29,24 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav; +package org.ical4j.connector.dav; -import net.fortuna.ical4j.connector.ObjectCollection; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.enums.MediaType; -import net.fortuna.ical4j.connector.dav.enums.ResourceType; -import net.fortuna.ical4j.connector.dav.property.BaseDavPropertyName; -import net.fortuna.ical4j.connector.dav.property.CalDavPropertyName; -import net.fortuna.ical4j.connector.dav.response.PropFindResponseHandler; -import org.apache.http.HttpResponse; import org.apache.http.client.HttpResponseException; -import org.apache.http.client.config.RequestConfig; import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.client.methods.HttpDelete; -import org.apache.jackrabbit.webdav.client.methods.HttpPropfind; -import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; import org.apache.jackrabbit.webdav.property.DavPropertySet; import org.apache.jackrabbit.webdav.security.SecurityConstants; +import org.ical4j.connector.AbstractObjectCollection; +import org.ical4j.connector.MediaType; +import org.ical4j.connector.ObjectCollection; +import org.ical4j.connector.ObjectStoreException; +import org.ical4j.connector.dav.property.BaseDavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.ical4j.connector.dav.property.PropertyNameSets; +import org.ical4j.connector.dav.response.GetPropertyValue; +import org.ical4j.connector.event.ListenerList; +import org.ical4j.connector.event.ObjectCollectionListener; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -66,9 +64,9 @@ * * @author fortuna */ -public abstract class AbstractDavObjectCollection implements ObjectCollection { +abstract class AbstractDavObjectCollection extends AbstractObjectCollection { - private final AbstractDavObjectStore store; + private final AbstractDavObjectStore> store; private final String id; @@ -78,20 +76,23 @@ public abstract class AbstractDavObjectCollection implements ObjectCollection private boolean _isReadOnly; + private final ListenerList> listenerList; + /** * @param store the container store for the collection * @param id collection identifier */ - public AbstractDavObjectCollection(AbstractDavObjectStore store, String id) { + public AbstractDavObjectCollection(AbstractDavObjectStore> store, String id) { this.store = store; this.id = id; this.properties = new DavPropertySet(); + this.listenerList = new ListenerList<>(); } /** * @return the store */ - public final AbstractDavObjectStore getStore() { + public final AbstractDavObjectStore> getStore() { return store; } @@ -105,13 +106,7 @@ public final String getId() { /** * @return the absolute collection path */ - public final String getPath() { -// FIXME fix for CGP... - if (!getId().endsWith("/")) { - return getId() + "/"; - } - return getId(); - } + abstract String getPath(); /** * Returns a list of the kinds of resource type for this collection. For example, for a collection that supports @@ -123,13 +118,13 @@ public ResourceType[] getResourceTypes() { try { ArrayList resourceTypeProp; - resourceTypeProp = getProperty(BaseDavPropertyName.RESOURCETYPE, ArrayList.class); + resourceTypeProp = getProperty(DavPropertyName.RESOURCETYPE, ArrayList.class); if (resourceTypeProp != null) { - for (Node child : resourceTypeProp) { + for (var child : resourceTypeProp) { if (child instanceof Element) { - String nameNode = child.getNodeName(); + var nameNode = child.getNodeName(); if (nameNode != null) { - ResourceType type = ResourceType.findByDescription(nameNode); + var type = ResourceType.findByDescription(nameNode); if (type != null) { resourceTypes.add(type); } @@ -156,13 +151,13 @@ public MediaType[] getSupportedMediaTypes() { ArrayList mediaTypeProp; mediaTypeProp = getProperty(CalDavPropertyName.SUPPORTED_CALENDAR_DATA, ArrayList.class); if (mediaTypeProp != null) { - for (Node child : mediaTypeProp) { + for (var child : mediaTypeProp) { if (child instanceof Element) { - String nameNode = child.getNodeName(); + var nameNode = child.getNodeName(); if ((nameNode != null) && ("calendar-data".equals(nameNode))) { - String contentType = ((Element) child).getAttribute("content-type"); - String version = ((Element) child).getAttribute("version"); - MediaType type = MediaType.findByContentTypeAndVersion(contentType, version); + var contentType = ((Element) child).getAttribute("content-type"); + var version = ((Element) child).getAttribute("version"); + var type = MediaType.findByContentTypeAndVersion(contentType, version); if (type != null) { mediaTypes.add(type); } @@ -170,7 +165,7 @@ public MediaType[] getSupportedMediaTypes() { } } } - return mediaTypes.toArray(new MediaType[mediaTypes.size()]); + return mediaTypes.toArray(new MediaType[0]); } catch (ObjectStoreException | IOException | DavException e) { throw new RuntimeException(e); } @@ -181,11 +176,11 @@ public MediaType[] getSupportedMediaTypes() { */ public Long getQuotaAvailableBytes() { try { - Long calTimezoneProp = getProperty(BaseDavPropertyName.QUOTA_AVAILABLE_BYTES, Long.class); + var calTimezoneProp = getProperty(BaseDavPropertyName.QUOTA_AVAILABLE_BYTES, Long.class); if (calTimezoneProp != null) { return calTimezoneProp; } - return new Long(0); + return 0L; } catch (ObjectStoreException | IOException | DavException e) { throw new RuntimeException(e); } @@ -196,11 +191,11 @@ public Long getQuotaAvailableBytes() { */ public Long getQuotaUsedBytes() { try { - Long calTimezoneProp = getProperty(BaseDavPropertyName.QUOTA_USED_BYTES, Long.class); + var calTimezoneProp = getProperty(BaseDavPropertyName.QUOTA_USED_BYTES, Long.class); if (calTimezoneProp != null) { return calTimezoneProp; } - return new Long(0); + return 0L; } catch (ObjectStoreException | IOException | DavException e) { throw new RuntimeException(e); } @@ -212,12 +207,12 @@ public Long getQuotaUsedBytes() { public String getOwnerHref() { String ownerHref = null; try { - ArrayList ownerProp; + List ownerProp; ownerProp = getProperty(SecurityConstants.OWNER, ArrayList.class); if (ownerProp != null) { - for (Node child : ownerProp) { + for (var child : ownerProp) { if (child instanceof Element) { - String nameNode = child.getNodeName(); + var nameNode = child.getNodeName(); if ((nameNode != null) && ("href".equals(nameNode))) { ownerHref = child.getTextContent(); } @@ -235,20 +230,11 @@ public String getOwnerHref() { */ public String getOwnerName() { if ((_ownerName == null) && (getOwnerHref() != null)) { + var nameSet = new DavPropertyNameSet(); + nameSet.add(DavPropertyName.DISPLAYNAME); try { - DavPropertyNameSet nameSet = new DavPropertyNameSet(); - nameSet.add(DavPropertyName.DISPLAYNAME); - HttpPropfind aGet = new HttpPropfind(getOwnerHref(), nameSet, 0); - - RequestConfig config = RequestConfig.copy(aGet.getConfig()).setAuthenticationEnabled(true).build(); - aGet.setConfig(config); - - PropFindResponseHandler responseHandler = new PropFindResponseHandler(aGet); - responseHandler.accept(getStore().getClient().execute(aGet)); - DavProperty displayNameProp = responseHandler.getPropertySet().get(DavPropertyName.DISPLAYNAME); - if (displayNameProp != null) { - _ownerName = (String)displayNameProp.getValue(); - } + _ownerName = getStore().getClient().propFind(getOwnerHref(), + nameSet, new GetPropertyValue<>()); } catch (IOException e) { throw new RuntimeException(e); } @@ -276,10 +262,10 @@ public void setReadOnly(boolean isReadOnly) { @SuppressWarnings({ "unchecked", "rawtypes" }) public final

P getProperty(DavPropertyName propertyName, Class

type) throws IOException, ObjectStoreException, DavException { - - DavPropertySet props = properties; + + var props = properties; if (props.get(propertyName) != null) { - Object value = props.get(propertyName).getValue(); + var value = props.get(propertyName).getValue(); try { if (Collection.class.isAssignableFrom(type)) { P result = type.newInstance(); @@ -307,12 +293,11 @@ public final

P getProperty(DavPropertyName propertyName, Class

type) * @throws IOException if there is a communication error * @throws ObjectStoreException where an unexpected error occurs */ - public final void delete() throws HttpResponseException, IOException, ObjectStoreException { - HttpDelete deleteMethod = new HttpDelete(getPath()); - HttpResponse httpResponse = getStore().getClient().execute(deleteMethod); - if (!deleteMethod.succeeded(httpResponse)) { - throw new ObjectStoreException(httpResponse.getStatusLine().getStatusCode() + ": " - + httpResponse.getStatusLine().getReasonPhrase()); + public final void delete() throws ObjectStoreException { + try { + getStore().getClient().delete(getPath()); + } catch (DavException | IOException e) { + throw new ObjectStoreException("Failed to delete resource", e); } } @@ -323,11 +308,11 @@ public final void delete() throws HttpResponseException, IOException, ObjectStor * @throws ObjectStoreException where an unexpected error occurs */ public final boolean exists() throws HttpResponseException, IOException, ObjectStoreException { - DavPropertyNameSet principalsProps = CalDavCalendarCollection.propertiesForFetch(); - HttpPropfind getMethod = new HttpPropfind(getPath(), principalsProps, 0); + return !getStore().getClient().propFind(getPath(), PropertyNameSets.PROPFIND_CALENDAR).isEmpty(); + } - PropFindResponseHandler responseHandler = new PropFindResponseHandler(getMethod); - responseHandler.accept(getStore().getClient().execute(getMethod)); - return responseHandler.exists(); + @Override + public ListenerList> getObjectCollectionListeners() { + return listenerList; } } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/AbstractDavObjectStore.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavObjectStore.java similarity index 59% rename from src/main/java/net/fortuna/ical4j/connector/dav/AbstractDavObjectStore.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavObjectStore.java index 3d820d3b..386efafa 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/AbstractDavObjectStore.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavObjectStore.java @@ -29,14 +29,13 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav; +package org.ical4j.connector.dav; -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.ObjectCollection; -import net.fortuna.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.enums.SupportedFeature; import net.fortuna.ical4j.util.Configurator; +import org.ical4j.connector.AbstractObjectStore; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.ObjectCollection; +import org.ical4j.connector.ObjectStoreException; import java.io.IOException; import java.net.URL; @@ -50,24 +49,33 @@ * * @author fortuna */ -public abstract class AbstractDavObjectStore> implements ObjectStore { +abstract class AbstractDavObjectStore> extends AbstractObjectStore { + /** + * Factory used to create new client instances on connect.. + */ private final DavClientFactory clientFactory; - private DavClient davClient; - - private String username; - private String bearerAuth; - - private final URL rootUrl; - - private List supportedFeatures; - + /** + * URL of the target DAV server. + */ + private final URL rootUrl; + /** * Server implementation-specific path resolution. */ protected final PathResolver pathResolver; + /** + * DAV client instance initialised on connect. A new instance is created each time a connect + * method is invoked. + */ + private DefaultDavClient davClient; + + private DavSessionConfiguration sessionConfiguration; + + private List supportedFeatures; + /** * @param url the URL of a CalDAV server instance * @param pathResolver the path resolver for the CalDAV server type @@ -75,66 +83,55 @@ public abstract class AbstractDavObjectStore> impl public AbstractDavObjectStore(URL url, PathResolver pathResolver) { this.rootUrl = url; this.pathResolver = pathResolver; - this.clientFactory = new DavClientFactory("true".equals(Configurator.getProperty("ical4j.connector.dav.preemptiveauth").orElse("false"))); + this.clientFactory = new DavClientFactory() + .withPreemptiveAuth("true".equals(Configurator.getProperty("ical4j.connector.dav.preemptiveauth") + .orElse("false"))); } /** - * @return the path + * Reconnect with an existing session configuration. */ - public final String getPath() { - return pathResolver == null ? rootUrl.getFile() : pathResolver.getUserPath( getUserName() ); + public final boolean connect() throws ObjectStoreException { + return connect(sessionConfiguration); } /** - * {@inheritDoc} + * + * @param bearerAuth + * @return + * @throws ObjectStoreException + * + * @deprecated use {@link AbstractDavObjectStore#connect(DavSessionConfiguration)} instead. */ - public final boolean connect() throws ObjectStoreException { - final String principalPath = pathResolver.getPrincipalPath(getUserName()); - final String userPath = pathResolver.getUserPath(getUserName()); - davClient = clientFactory.newInstance(rootUrl, principalPath, userPath); - davClient.begin(); - - return true; - } - - - public final boolean connect( String bearerAuth ) throws ObjectStoreException { - try { - davClient = clientFactory.newInstance(rootUrl, rootUrl.getFile(), rootUrl.getFile()); - davClient.begin( bearerAuth ); - - this.bearerAuth = bearerAuth; - } catch (IOException ioe) { - throw new ObjectStoreException( ioe ); - } catch (FailedOperationException foe) { - throw new ObjectStoreException( foe ); - } - - return true; + @Deprecated + public final boolean connect(String bearerAuth) throws ObjectStoreException { + return connect(new DavSessionConfiguration().withBearerAuth(bearerAuth)); } /** * {@inheritDoc} * @throws FailedOperationException - * @throws IOException + * @throws IOException + * + * @deprecated use {@link AbstractDavObjectStore#connect(DavSessionConfiguration)} instead. */ + @Deprecated public final boolean connect(String username, char[] password) throws ObjectStoreException { + return connect(new DavSessionConfiguration().withUser(username).withPassword(password)); + } + + public final boolean connect(DavSessionConfiguration sessionConfiguration) throws ObjectStoreException { + this.sessionConfiguration = sessionConfiguration; try { - this.username = username; - - final String principalPath = pathResolver.getPrincipalPath(username); - final String userPath = pathResolver.getUserPath(username); - davClient = clientFactory.newInstance(rootUrl, principalPath, userPath); - supportedFeatures = davClient.begin(username, password); - } - catch (IOException ioe) { + davClient = clientFactory.newInstance(rootUrl); + if (sessionConfiguration.getCredentialsProvider() != null) { + davClient.begin(sessionConfiguration.getCredentialsProvider()); + } + supportedFeatures = davClient.getSupportedFeatures(); + } catch (IOException ioe) { throw new ObjectStoreException(ioe); } - catch (FailedOperationException foe) { - throw new ObjectStoreException(foe); - } - return true; } @@ -143,7 +140,7 @@ public final boolean connect(String username, char[] password) throws ObjectStor */ public final void disconnect() { davClient = null; - username = null; + supportedFeatures = null; } /** @@ -158,19 +155,22 @@ public final boolean isConnected() { * * @return the username stored in the HTTP credentials * @author Pascal Robert + * + * @deprecated use {@link AbstractDavObjectStore#getSessionConfiguration()} instead. */ + @Deprecated protected String getUserName() { - return username; + return sessionConfiguration.getUser(); } - public DavClient getClient() { + public DefaultDavClient getClient() { return davClient; } - - public URL getHostURL() { - return rootUrl; + + public DavSessionConfiguration getSessionConfiguration() { + return sessionConfiguration; } - + /** * Returns a list of supported features, based on the DAV header in the response * of the connect call. @@ -181,7 +181,6 @@ public List supportedFeatures() { } public boolean isSupportCalendarProxy() { - return supportedFeatures.contains(SupportedFeature.CALENDAR_PROXY) ? true: false; + return supportedFeatures.contains(SupportedFeature.CALENDAR_PROXY); } - } diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavResource.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavResource.java new file mode 100644 index 00000000..41aa2c2e --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/AbstractDavResource.java @@ -0,0 +1,250 @@ +package org.ical4j.connector.dav; + +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.*; +import org.apache.jackrabbit.webdav.lock.*; +import org.apache.jackrabbit.webdav.property.*; +import org.ical4j.connector.dav.response.GetPropertyValue; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.jackrabbit.webdav.property.DavPropertyName.DISPLAYNAME; +import static org.apache.jackrabbit.webdav.property.DavPropertyName.GETLASTMODIFIED; + +abstract class AbstractDavResource implements DavResource { + + private final DavResourceFactory factory; + + private final DavResourceLocator locator; + + private final DavResource parent; + + private final List children; + + protected final DavPropertySet properties; + + protected final T client; + + private boolean exists; + + public AbstractDavResource(DavResourceFactory factory, DavResourceLocator locator, + DavPropertySet properties, T client, DavResource parent) { + this.factory = factory; + this.locator = locator; + this.properties = properties; + this.client = client; + this.parent = parent; + this.children = new ArrayList<>(); + } + + @Override + public String getComplianceClass() { + return ""; + } + + @Override + public String getSupportedMethods() { + return METHODS; + } + + @Override + public String getHref() { + return locator.getHref(isCollection()); + } + + @Override + public boolean isCollection() { + return "collection".equals(getPropertyValue(DavPropertyName.RESOURCETYPE)); + } + + @Override + public String getDisplayName() { + return getPropertyValue(DISPLAYNAME); + } + + @Override + public String getResourcePath() { + return locator.getResourcePath(); + } + + @Override + public boolean exists() { + if (!exists) { + try { + exists = client.head(getResourcePath(), response -> + response.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_FOUND); + } catch (IOException e) { + LoggerFactory.getLogger(AbstractDavResource.class).error("Server request failed", e); + } + } + return exists; + } + + @Override + public long getModificationTime() { + Long lastModified = getPropertyValue(GETLASTMODIFIED); + return lastModified != null ? lastModified : -1; + } + + @Override + public DavPropertySet getProperties() { + var copy = new DavPropertySet(); + copy.addAll(properties); + return copy; + } + + @Override + public DavPropertyName[] getPropertyNames() { + try { + return this.client.propFindAll(getResourcePath()).get(0).getProperties().getPropertyNames(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Checks for cached property value, and if not found uses PROPFIND query to DAV server. + * @param name a property name + * @return a property value, or null if property doesn't exist + */ + @Override + public DavProperty getProperty(DavPropertyName name) { + DavProperty property = properties.get(name); + if (property == null) { + try { + var result = client.propFind(getResourcePath(), name).get(0).getProperties(); + this.properties.addAll(result); + property = properties.get(name); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return property; + } + + private

P getPropertyValue(DavPropertyName name) { + var nameSet = new DavPropertyNameSet(); + nameSet.add(name); + try { + return client.propFind(getResourcePath(), nameSet, new GetPropertyValue<>()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public DavResourceLocator getLocator() { + return locator; + } + + @Override + public DavResourceFactory getFactory() { + return factory; + } + + @Override + public void setProperty(DavProperty property) throws DavException { + try { + this.client.propPatchSet(getResourcePath(), property); + this.properties.add(property); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void removeProperty(DavPropertyName propertyName) throws DavException { + try { + this.properties.remove(propertyName); + this.client.propPatchRemove(getResourcePath(), propertyName); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public MultiStatusResponse alterProperties(List changeList) throws DavException { + try { + return this.client.propPatch(getResourcePath(), changeList); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public DavResource getCollection() { + return parent; + } + + @Override + public DavResourceIterator getMembers() { + return new DavResourceIteratorImpl(children); + } + + @Override + public void move(DavResource destination) throws DavException { + try { + client.move(getResourcePath(), destination.getResourcePath()); + this.exists = false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void copy(DavResource destination, boolean shallow) throws DavException { + try { + client.copy(getResourcePath(), destination.getResourcePath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isLockable(Type type, Scope scope) { + return false; + } + + @Override + public boolean hasLock(Type type, Scope scope) { + return false; + } + + @Override + public ActiveLock getLock(Type type, Scope scope) { + return null; + } + + @Override + public ActiveLock[] getLocks() { + return new ActiveLock[0]; + } + + @Override + public ActiveLock lock(LockInfo reqLockInfo) throws DavException { + return null; + } + + @Override + public ActiveLock refreshLock(LockInfo reqLockInfo, String lockToken) throws DavException { + return null; + } + + @Override + public void unlock(String lockToken) throws DavException { + + } + + @Override + public void addLockManager(LockManager lockmgr) { + + } + + @Override + public DavSession getSession() { + return null; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavCalendarCollection.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavCalendarCollection.java new file mode 100644 index 00000000..ee13e802 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavCalendarCollection.java @@ -0,0 +1,517 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.*; +import net.fortuna.ical4j.model.property.Uid; +import net.fortuna.ical4j.util.Calendars; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.version.report.ReportInfo; +import org.ical4j.connector.*; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.ical4j.connector.dav.property.DavPropertyBuilder; +import org.ical4j.connector.dav.property.ICalPropertyName; +import org.ical4j.connector.dav.property.PropertyNameSets; +import org.ical4j.connector.dav.request.CalendarQuery; +import org.ical4j.connector.dav.request.EventQuery; +import org.ical4j.connector.dav.response.GetCalendarData; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.StringReader; +import java.net.URI; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.apache.jackrabbit.webdav.property.DavPropertyName.DISPLAYNAME; + +/** + * $Id$ + * + * Created on 24/02/2008 + * + * @author Ben + * + */ +public class CalDavCalendarCollection extends AbstractDavObjectCollection implements CalendarCollection { + + /** + * Only {@link CalDavCalendarStore} should be calling this, so default modifier is applied. + * + * @param calDavCalendarStore + * @param id + */ + CalDavCalendarCollection(CalDavCalendarStore calDavCalendarStore, String id) { + this(calDavCalendarStore, id, id, ""); + } + + /** + * Only {@link CalDavCalendarStore} should be calling this, so default modifier is applied. + * + * @param calDavCalendarStore + * @param id + * @param displayName + * @param description + */ + CalDavCalendarCollection(CalDavCalendarStore calDavCalendarStore, String id, String displayName, String description) { + + super(calDavCalendarStore, id); + properties.add(new DavPropertyBuilder<>().name(DISPLAYNAME).value(displayName).build()); + properties.add(new DavPropertyBuilder<>().name(CalDavPropertyName.CALENDAR_DESCRIPTION).value(description).build()); + } + + CalDavCalendarCollection(CalDavCalendarStore calDavCalendarStore, String id, DavPropertySet _properties) { + this(calDavCalendarStore, id, null, null); + this.properties = _properties; + } + + @Override + String getPath() { + return getStore().pathResolver.getCalendarPath(getId(), getStore().getSessionConfiguration().getWorkspace()); + } + + /** + * Creates this collection on the CalDAV server. + * + * @throws IOException + * @throws ObjectStoreException + */ + final void create() throws IOException, ObjectStoreException { + try { + getStore().getClient().mkCalendar(getPath(), properties); + } catch (DavException e) { + throw new ObjectStoreException("Failed to create calendar collection", e); + } + } + + @Override + public List listObjectUIDs() { + //TODO: extract UIDs from calendar objects.. + return null; + } + + /** + * @return an array of calendar objects + * @deprecated Use the getEvents() method + * @see ObjectCollection#getAll(String...) + */ + @Deprecated + public Iterable getCalendars() { + return getComponentsByType(Component.VEVENT); + } + + /** + * @return and array of calendar objects + */ + public Iterable getEvents() { + return getComponentsByType(Component.VEVENT); + } + + /** + * @return and array of calendar objects + */ + public Iterable getTasks() { + return getComponentsByType(Component.VTODO); + } + + /** + * @param componentType + * the type of component + * @return and array of calendar objects + */ + public Iterable getComponentsByType(String componentType) { + try { + var info = new ReportInfo(CalDavPropertyName.CALENDAR_QUERY, 1, + PropertyNameSets.REPORT_CALENDAR); + info.setContentElement(new CalendarQuery(componentType).build()); + + List calendars = getStore().getClient().report(getPath(), info, new GetCalendarData()); + return calendars; + } catch (IOException | ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a human-readable description of the calendar collection. + */ + public String getDescription() { + try { + return getProperty(CalDavPropertyName.CALENDAR_DESCRIPTION, String.class); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Human-readable name of the collection. + */ + public String getDisplayName() { + try { + return getProperty(DISPLAYNAME, String.class); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a numeric value indicating the maximum number of ATTENDEE properties in any instance of a calendar + * object resource stored in a calendar collection. + */ + public Integer getMaxAttendeesPerInstance() { + try { + return getProperty(CalDavPropertyName.MAX_ATTENDEES_PER_INSTANCE, Integer.class); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a DATE-TIME value indicating the latest date and time (in UTC) that the server is willing to accept for + * any DATE or DATE-TIME value in a calendar object resource stored in a calendar collection. + */ + public Instant getMaxDateTime() { + try { + return Instant.from(TemporalAdapter.parse( + getProperty(CalDavPropertyName.MAX_DATE_TIME, String.class)).getTemporal()); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a numeric value indicating the maximum number of recurrence instances that a calendar object resource + * stored in a calendar collection can generate. + */ + public Integer getMaxInstances() { + try { + return getProperty(CalDavPropertyName.MAX_INSTANCES, Integer.class); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a numeric value indicating the maximum size of a resource in octets that the server is willing to accept + * when a calendar object resource is stored in a calendar collection. 0 = no limits. + */ + public long getMaxResourceSize() { + try { + var size = getProperty(CalDavPropertyName.MAX_RESOURCE_SIZE, Long.class); + if (size != null) { + return size; + } + return 0; + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a DATE-TIME value indicating the earliest date and time (in UTC) that the server is willing to accept + * for any DATE or DATE-TIME value in a calendar object resource stored in a calendar collection. + */ + public Instant getMinDateTime() { + try { + return Instant.from(TemporalAdapter.parse( + getProperty(CalDavPropertyName.MIN_DATE_TIME, String.class)).getTemporal()); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Get the list of calendar components (VEVENT, VTODO, etc.) that this collection supports. + */ + public String[] getSupportedComponentTypes() { + List supportedComponents = new ArrayList(); + + ArrayList supportedCalCompSetProp; + try { + supportedCalCompSetProp = getProperty(CalDavPropertyName.SUPPORTED_CALENDAR_COMPONENT_SET, ArrayList.class); + if (supportedCalCompSetProp != null) { + for (var child : supportedCalCompSetProp) { + if (child instanceof Element) { + var nameNode = child.getAttributes().getNamedItem("name"); + if (nameNode != null) { + supportedComponents.add(nameNode.getTextContent()); + } + } + } + } + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + + return supportedComponents.toArray(new String[supportedComponents.size()]); + } + + /** + * The CALDAV:calendar-timezone property is used to specify the time zone the server should rely on to resolve + * "date" values and "date with local time" values (i.e., floating time) to "date with UTC time" values. + */ + public Calendar getTimeZone() { + try { + String calTimezoneProp = getProperty(CalDavPropertyName.CALENDAR_TIMEZONE, String.class); + + if (calTimezoneProp != null) { + var builder = new CalendarBuilder(); + return builder.build(new StringReader(calTimezoneProp)); + } + return new Calendar(); + } catch (ObjectStoreException | IOException | DavException | ParserException e) { + throw new RuntimeException(e); + } + } + + public String getColor() { + try { + return getProperty(ICalPropertyName.CALENDAR_COLOR, String.class); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + public int getOrder() { + try { + return getProperty(ICalPropertyName.CALENDAR_ORDER, Integer.class); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Add a new calendar object in the collection. Creation will be done on the server right away. + */ + @Override + public String add(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { + writeCalendarOnServer(calendar, true); + return calendar.getRequiredProperty(Property.UID).getValue(); + } + + /** + * Stores the specified calendar in this collection, using the specified URI. + * @param uri the URI (relative to this collection's path) where the calendar is to be stored + * @param calendar a calendar object instance to be added to the collection + * @throws ObjectStoreException when an unexpected error occurs (implementation-specific) + */ + public void addCalendar(String uri, Calendar calendar) throws ObjectStoreException { + writeCalendarOnServer(uri, calendar, true); + } + + /** + * Update a calendar object in the collection. Update will be send to the server right away. + * @param calendar + * @throws ObjectStoreException + * @throws ConstraintViolationException + */ + public void updateCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { + writeCalendarOnServer(calendar, false); + } + + /** + * Update a calendar object in the collection. Update will be send to the server right away. + * @param calendar + * @throws ObjectStoreException + */ + public void updateCalendar(String uri, Calendar calendar) throws ObjectStoreException { + writeCalendarOnServer(uri, calendar, false); + } + + public void writeCalendarOnServer(Calendar calendar, boolean isNew) throws ObjectStoreException, ConstraintViolationException { + var uid = Calendars.getUid(calendar); + writeCalendarOnServer(defaultUriFromUid(uid.getValue()), calendar, isNew); + } + + public void writeCalendarOnServer(String uri, Calendar calendar, boolean isNew) throws ObjectStoreException { + var path = getPath(); + if (!path.endsWith("/")) { + path = path.concat("/"); + } + + try { + // TODO: get ETag and Schedule-Tag headers and store them locally + getStore().getClient().put(path + uri, calendar, isNew ? null : "*"); + } catch (IOException | FailedOperationException e) { + throw new ObjectStoreException("Error creating calendar on server", e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Optional get(String uid) { + try { + return Optional.of(getCalendarFromUri(defaultUriFromUid(uid))); + } catch (ObjectNotFoundException e) { + return Optional.empty(); + } + } + + /** + * Returns the calendar object located at the specified URI. + * @param uri the URI (relative to this collection's path) where the calendar is to be found + * @return a calendar object or null if no calendar exists under the specified URI + */ + public Calendar getCalendarFromUri(String uri) throws ObjectNotFoundException { + var path = getPath(); + if (!path.endsWith("/")) { + path = path.concat("/"); + } + try { + return getStore().getClient().getCalendar(path + uri); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * {@inheritDoc} + */ + public List removeAll(String... uid) { + List result = new ArrayList<>(); + for (var u : uid) { + try { + result.add(removeCalendarFromUri(defaultUriFromUid(u))); + } catch (FailedOperationException | ObjectStoreException | ObjectNotFoundException e) { + throw new RuntimeException(e); + } + } + return result; + } + + /** + * @param uri the URI (relative to this collection's path) where the calendar is to be found + * @return the calendar that was successfully removed from the collection + * @throws ObjectStoreException where an unexpected error occurs + */ + public Calendar removeCalendarFromUri(String uri) throws FailedOperationException, ObjectStoreException, ObjectNotFoundException { + var calendar = getCalendarFromUri(uri); + try { + getStore().getClient().delete(getPath() + "/" + uri); + } catch (IOException | DavException e) { + throw new ObjectStoreException(e); + } + return calendar; + } + + /** + * {@inheritDoc} + */ + @Override + public final Uid[] merge(Calendar calendar) throws FailedOperationException, ObjectStoreException { + List uids = new ArrayList<>(); + try { + var uidCalendars = Calendars.split(calendar); + for (int i = 0; i < uidCalendars.length; i++) { + add(uidCalendars[i]); + uids.add(uidCalendars[i].getRequiredProperty(Property.UID)); + } + } catch (ConstraintViolationException cve) { + throw new FailedOperationException("Invalid calendar format", cve); + } + return uids.toArray(new Uid[0]); + } + + /** + * {@inheritDoc} + */ + public Calendar export() { + throw new UnsupportedOperationException("not implemented"); + } + + /** + * {@inheritDoc} + */ + public Iterable getAll() throws ObjectStoreException { + return getComponentsByType(Component.VEVENT); + } + + /** + * Get a list of calendar objects of VEVENT type for a specific time period. + * + * @param startTime + * @param endTime + * @return + * @throws IOException + * @throws DavException + * @throws ParserConfigurationException + * @throws ParserException + */ + public List getEventsForTimePeriod(DateTime startTime, DateTime endTime) + throws IOException, DavException, ParserConfigurationException, ParserException { + + return getStore().getClient().report(this.getPath(), new EventQuery(1) + .withStartTime(startTime).withEndTime(endTime), new GetCalendarData()); + } + + /** + * TODO: implement calendar-multiget to fetch objects based on href + * @param hrefs + * @param calData + * @return + * @throws IOException + * @throws DavException + * @throws ParserConfigurationException + * @throws ParserException + */ + public Calendar[] getObjectsByMultiget(ArrayList hrefs, Element calData) + throws IOException, DavException, ParserConfigurationException, ParserException { + return new Calendar[0]; + } + + /** + * TODO: implement free-busy-query + * @return + */ + public Calendar[] doFreeBusyQuery() { + return new Calendar[0]; + } + + @Override + public String toString() { + return "Display Name: " + getDisplayName() + ", id: " + getId(); + } + + + private String defaultUriFromUid(String uid) { + return uid + ".ics"; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavCalendarStore.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavCalendarStore.java new file mode 100644 index 00000000..f414a30b --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavCalendarStore.java @@ -0,0 +1,418 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.component.VFreeBusy; +import net.fortuna.ical4j.model.parameter.CuType; +import net.fortuna.ical4j.model.property.*; +import net.fortuna.ical4j.model.property.immutable.ImmutableCalScale; +import net.fortuna.ical4j.model.property.immutable.ImmutableMethod; +import net.fortuna.ical4j.model.property.immutable.ImmutableVersion; +import net.fortuna.ical4j.util.FixedUidGenerator; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.security.SecurityConstants; +import org.apache.jackrabbit.webdav.version.report.ReportInfo; +import org.ical4j.connector.CalendarCollection; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStore; +import org.ical4j.connector.ObjectStoreException; +import org.ical4j.connector.dav.method.PrincipalPropertySearchInfo; +import org.ical4j.connector.dav.property.BaseDavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.ical4j.connector.dav.property.PropertyNameSets; +import org.ical4j.connector.dav.request.ExpandPropertyQuery; +import org.ical4j.connector.dav.response.GetCalDavCollections; +import org.ical4j.connector.dav.response.GetCollections; +import org.ical4j.connector.dav.response.GetPropertyValue; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.ParseException; +import java.util.*; +import java.util.stream.Collectors; + +import static org.ical4j.connector.dav.ResourceType.*; + +/** + * $Id$ + * + * Created on 24/02/2008 + * + * @author Ben + * + */ +public final class CalDavCalendarStore extends AbstractDavObjectStore implements + ObjectStore { + + private final String prodId; + private String displayName; + + /** + * @param prodId application product identifier + * @param url the URL of a CalDAV server instance + * @param pathResolver the path resolver for the CalDAV server type + */ + public CalDavCalendarStore(String prodId, URL url, PathResolver pathResolver) { + super(url, pathResolver); + this.prodId = prodId; + } + + /** + * {@inheritDoc} + */ + @Override + public CalDavCalendarCollection addCollection(String id) throws ObjectStoreException { + var collection = new CalDavCalendarCollection(this, id); + try { + collection.create(); + } catch (IOException e) { + throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); + } + return collection; + } + + @Override + public CalDavCalendarCollection addCollection(String id, String workspace) throws ObjectStoreException { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + /** + * {@inheritDoc} + */ + @Override + public CalDavCalendarCollection addCollection(String id, String displayName, String description, + String[] supportedComponents, Calendar timezone) throws ObjectStoreException { + + var collection = new CalDavCalendarCollection(this, id, displayName, description); + try { + collection.create(); + } catch (IOException e) { + throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); + } + return collection; + } + + @Override + public CalDavCalendarCollection addCollection(String id, String displayName, String description, + String[] supportedComponents, Calendar timezone, + String workspace) throws ObjectStoreException { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + /** + * {@inheritDoc} + */ + public CalDavCalendarCollection addCollection(String id, DavPropertySet properties) throws ObjectStoreException { + var collection = new CalDavCalendarCollection(this, id, properties); + try { + collection.create(); + } catch (IOException e) { + throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); + } + return collection; + } + + /** + * {@inheritDoc} + */ + @Override + public CalDavCalendarCollection getCollection(String id) throws ObjectStoreException, ObjectNotFoundException { + return getCollection(id, DEFAULT_WORKSPACE); + } + + @Override + public CalDavCalendarCollection getCollection(String id, String workspace) throws ObjectStoreException, ObjectNotFoundException { + try { + var resourcePath = pathResolver.getCalendarPath(id, workspace); + Map res = getClient().propFind(resourcePath, + PropertyNameSets.PROPFIND_CALENDAR, + new GetCollections(CALENDAR, CALENDAR_PROXY_READ, CALENDAR_PROXY_WRITE)); + if (!res.isEmpty()) { + var props = res.entrySet().iterator().next().getValue(); + return new CalDavCalendarCollection(this, id, props); +// .entrySet().stream() +// .map(e -> new CalDavCalendarCollection(this, e.getKey(), e.getValue())) +// .collect(Collectors.toList()).get(0); + } else { + return null; + } + } catch (IOException e) { + throw new ObjectStoreException(String.format("unable to get collection '%s'", id), e); + } + } + + /** + * {@inheritDoc} + */ + public CalendarCollection merge(String id, CalendarCollection calendar) { + throw new UnsupportedOperationException("not implemented"); + } + + public String findCalendarHomeSet() throws ParserConfigurationException, IOException, DavException { + var propfindPath = pathResolver.getPrincipalPath(getSessionConfiguration().getUser()); + return findCalendarHomeSet(propfindPath); + } + + /** + * This method try to find the calendar-home-set attribute in the user's DAV principals. The calendar-home-set + * attribute is the URI of the main collection of calendars for the user. + * + * @return the URI for the main calendar collection + * @author Pascal Robert + * @throws ParserConfigurationException + * @throws IOException + * @throws DavException + */ + protected String findCalendarHomeSet(String propfindUri) throws IOException { + return getClient().propFind(propfindUri, PropertyNameSets.PROPFIND_CALENDAR_HOME, new GetPropertyValue<>()); + } + + /** + * This method will try to find all calendar collections available at the calendar-home-set URI of the user. + * + * @return An array of all available calendar collections + * @author Pascal Robert + * @throws ParserConfigurationException where the parse is not configured correctly + * @throws IOException where a communications error occurs + * @throws DavException where an error occurs calling the DAV method + */ + @Override + public List getCollections() throws ObjectStoreException, ObjectNotFoundException { + try { + var calHomeSetPath = findCalendarHomeSet(); + if (calHomeSetPath == null) { + throw new ObjectNotFoundException("No calendar-home-set attribute found for the user"); + } + return getCollectionsForHomeSet(this, calHomeSetPath); + } catch (DavException | IOException | ParserConfigurationException de) { + throw new ObjectStoreException(de); + } + } + + @Override + public List getCollections(String workspace) throws ObjectStoreException, ObjectNotFoundException { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + protected List getCollectionsForHomeSet(CalDavCalendarStore store, + String urlForcalendarHomeSet) throws IOException, DavException { + + return getClient().propFind(urlForcalendarHomeSet, PropertyNameSets.PROPFIND_CALENDAR, + new GetCollections(COLLECTION)).entrySet().stream() + .map(e -> new CalDavCalendarCollection(this, e.getKey(), e.getValue())) + .collect(Collectors.toList()); + } + + /** + * Get the list of available delegated collections, Apple's iCal style + * + * @return + * @throws Exception + */ + public List getDelegatedCollections() throws Exception { + List collections = new ArrayList(); + collections.addAll(getWriteDelegatedCollections()); + collections.addAll(getReadOnlyDelegatedCollections()); + return collections; + } + + protected List getDelegatedCollections(ExpandPropertyQuery.Type type) throws Exception { + + var methodUri = this.pathResolver.getPrincipalPath(getSessionConfiguration().getUser()); + + var expandPropertyReport = new ExpandPropertyQuery(type) + .withPropertyName(DavPropertyName.DISPLAYNAME) + .withPropertyName(SecurityConstants.PRINCIPAL_URL) + .withPropertyName(CalDavPropertyName.USER_ADDRESS_SET); + + var rinfo = new ReportInfo(BaseDavPropertyName.EXPAND_PROPERTY, 0); + rinfo.setContentElement(expandPropertyReport.build()); + + return getClient().report(methodUri, rinfo, new GetCalDavCollections()); + } + + public List getWriteDelegatedCollections() throws Exception { + List collections = getDelegatedCollections(ExpandPropertyQuery.Type.PROXY_WRITE_FOR); + return collections; + } + + public List getReadOnlyDelegatedCollections() throws Exception { + List collections = getDelegatedCollections(ExpandPropertyQuery.Type.PROXY_READ_FOR); + return collections; + } + + /** + * {@inheritDoc} + */ + public CalDavCalendarCollection removeCollection(String id) throws ObjectStoreException, ObjectNotFoundException { + var collection = getCollection(id); + collection.delete(); + return collection; + } + + @Override + public List listWorkspaceIds() { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + // public CalendarCollection replace(String id, CalendarCollection calendar) { + // // TODO Auto-generated method stub + // return null; + // } + + /** + * @return the prodId + */ + final String getProdId() { + return prodId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String findScheduleOutbox() throws ParserConfigurationException, IOException, DavException { + var propfindUri = pathResolver.getPrincipalPath(getSessionConfiguration().getUser()); + var nameSet = new DavPropertyNameSet(); + nameSet.add(CalDavPropertyName.SCHEDULE_OUTBOX_URL); + return getClient().propFind(propfindUri, nameSet, new GetPropertyValue<>()); + } + + public String findScheduleInbox() throws ParserConfigurationException, IOException, DavException { + var propfindUri = pathResolver.getPrincipalPath(getSessionConfiguration().getUser()); + var nameSet = new DavPropertyNameSet(); + nameSet.add(CalDavPropertyName.SCHEDULE_INBOX_URL); + return getClient().propFind(propfindUri, nameSet, new GetPropertyValue<>()); + } + + /** + * This method will return free-busy information for each attendee. If the free-busy information can't be retrieve + * (for example, users on an foreign server), check the isSuccess method to see if free-busy lookup was successful. + * + * @author probert + */ + public List findFreeBusyInfoForAttendees(Organizer organizer, ArrayList attendees, + DtStart startTime, DtEnd endTime) throws ParserConfigurationException, IOException, DavException, + ParseException, ParserException, SAXException { + var ramdomizer = new Random(); + ArrayList responses = new ArrayList(); + + var calendar = new Calendar(); + calendar.add(new ProdId(getProdId())); + calendar.add(ImmutableVersion.VERSION_2_0); + calendar.add(ImmutableCalScale.GREGORIAN); + calendar.add(ImmutableMethod.REQUEST); + + var fbComponent = new VFreeBusy(); + + fbComponent.add(organizer); + + fbComponent.add(startTime); + fbComponent.add(endTime); + + var strAttendee = ""; + if (attendees != null) { + for (Iterator itrAttendee = attendees.iterator(); itrAttendee.hasNext();) { + var attendee = itrAttendee.next(); + fbComponent.add(attendee); + strAttendee += attendee.getValue() + ","; + } + strAttendee = strAttendee.substring(0, strAttendee.length() - 1); + } + + var ug = new FixedUidGenerator(ramdomizer.nextInt() + ""); + fbComponent.add(ug.generateUid()); + calendar.getComponents().add(fbComponent); + + return getClient().freeBusy(findScheduleOutbox(), calendar, organizer); + } + + public List getIndividuals(String nameToSearch) throws ParserConfigurationException, IOException, DavException, URISyntaxException { + return getUserTypes(CuType.INDIVIDUAL, nameToSearch); + } + + public List getRooms(String nameToSearch) throws ParserConfigurationException, IOException, DavException, URISyntaxException { + return getUserTypes(CuType.ROOM, nameToSearch); + } + + public List getAllRooms() throws ParserConfigurationException, IOException, DavException, URISyntaxException { + return getAllPrincipalsForType(CuType.ROOM); + } + + public List getAllResources() throws ParserConfigurationException, IOException, DavException, URISyntaxException { + return getAllPrincipalsForType(CuType.RESOURCE); + } + + public List getAllPrincipalsForType(CuType type) throws ParserConfigurationException, IOException, DavException, URISyntaxException { + return executePrincipalPropSearch(new org.ical4j.connector.dav.request.PrincipalPropertySearch(type).build()); + } + + /** + * Use this method to search for resources (individual, group, resource, room). + * For example, if you want to find all rooms that begins + * with "Room" in their email or email address, call this method with: + * + * getUserTypes(CuType.ROOM, "Room"); + * + * If nameToSearch is null, it will find all resources for the desired type. + * + * @param type + * @param nameToSearch + * @return + * @throws ParserConfigurationException + * @throws IOException + * @throws DavException + * @throws URISyntaxException + */ + protected List getUserTypes(CuType type, String nameToSearch) throws ParserConfigurationException, IOException, DavException, URISyntaxException { + return executePrincipalPropSearch(new org.ical4j.connector.dav.request.PrincipalPropertySearch(type, nameToSearch).build()); + } + + protected List executePrincipalPropSearch(Element principalPropSearch) throws DavException, IOException, URISyntaxException { + var rinfo = new PrincipalPropertySearchInfo(principalPropSearch, 0); + var methodUri = this.pathResolver.getPrincipalPath(getSessionConfiguration().getUser()); + return getClient().findPrincipals(methodUri, rinfo); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavLocatorFactory.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavLocatorFactory.java new file mode 100644 index 00000000..b3b3bf8e --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavLocatorFactory.java @@ -0,0 +1,23 @@ +package org.ical4j.connector.dav; + +import org.apache.jackrabbit.webdav.AbstractLocatorFactory; + +class CalDavLocatorFactory extends AbstractLocatorFactory { + + private final PathResolver pathResolver; + + public CalDavLocatorFactory(String pathPrefix, PathResolver pathResolver) { + super(pathPrefix); + this.pathResolver = pathResolver; + } + + @Override + protected String getRepositoryPath(String resourcePath, String wspPath) { + return pathResolver.getCalendarPath(resourcePath, wspPath); + } + + @Override + protected String getResourcePath(String repositoryPath, String wspPath) { + return pathResolver.getResourceName(repositoryPath, wspPath); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavResource.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavResource.java new file mode 100644 index 00000000..2c1c55f0 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavResource.java @@ -0,0 +1,35 @@ +package org.ical4j.connector.dav; + +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavResource; +import org.apache.jackrabbit.webdav.DavResourceFactory; +import org.apache.jackrabbit.webdav.DavResourceLocator; +import org.apache.jackrabbit.webdav.io.InputContext; +import org.apache.jackrabbit.webdav.io.OutputContext; +import org.apache.jackrabbit.webdav.property.DavPropertySet; + +import java.io.IOException; + +class CalDavResource extends AbstractDavResource { + + public CalDavResource(DavResourceFactory factory, DavResourceLocator locator, DavPropertySet properties, + CalDavSupport client, CalDavResource parent) { + + super(factory, locator, properties, client, parent); + } + + @Override + public void spool(OutputContext outputContext) throws IOException { + + } + + @Override + public void addMember(DavResource resource, InputContext inputContext) throws DavException { + + } + + @Override + public void removeMember(DavResource member) throws DavException { + + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavResourceFactory.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavResourceFactory.java new file mode 100644 index 00000000..b0392cbe --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavResourceFactory.java @@ -0,0 +1,41 @@ +package org.ical4j.connector.dav; + +import org.apache.jackrabbit.webdav.*; +import org.apache.jackrabbit.webdav.property.DavPropertySet; + +import java.net.MalformedURLException; + +class CalDavResourceFactory implements DavResourceFactory { + + private DavClientFactory clientFactory; + + public CalDavResourceFactory() { + this(new DavClientFactory().withPreemptiveAuth(true).withFollowRedirects(true)); + } + + public CalDavResourceFactory(DavClientFactory clientFactory) { + this.clientFactory = clientFactory; + } + + @Override + public DavResource createResource(DavResourceLocator locator, DavServletRequest request, + DavServletResponse response) throws DavException { + + try { + return new CalDavResource(this, locator, new DavPropertySet(), + clientFactory.newInstance(locator.getRepositoryPath()), null); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException { + try { + return new CalDavResource(this, locator, new DavPropertySet(), + clientFactory.newInstance(locator.getRepositoryPath()), null); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavSupport.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavSupport.java new file mode 100644 index 00000000..6e9e443b --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CalDavSupport.java @@ -0,0 +1,150 @@ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.model.Calendar; +import org.apache.http.client.ResponseHandler; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.version.report.ReportInfo; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.ObjectStoreException; +import org.ical4j.connector.dav.request.CalendarQuery; +import org.ical4j.connector.dav.response.GetCalendarResource; +import org.ical4j.connector.dav.response.ResourceProps; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public interface CalDavSupport extends WebDavSupport { + + /** + *

+     *    An HTTP request using the MKCALENDAR method creates a new calendar
+     *    collection resource.  A server MAY restrict calendar collection
+     *    creation to particular collections.
+     *
+     *    Support for MKCALENDAR on the server is only RECOMMENDED and not
+     *    REQUIRED because some calendar stores only support one calendar per
+     *    user (or principal), and those are typically pre-created for each
+     *    account.  However, servers and clients are strongly encouraged to
+     *    support MKCALENDAR whenever possible to allow users to create
+     *    multiple calendar collections to help organize their data better.
+     *
+     *    Clients SHOULD use the DAV:displayname property for a human-readable
+     *    name of the calendar.  Clients can either specify the value of the
+     *    DAV:displayname property in the request body of the MKCALENDAR
+     *    request, or alternatively issue a PROPPATCH request to change the
+     *    DAV:displayname property to the appropriate value immediately after
+     *    issuing the MKCALENDAR request.  Clients SHOULD NOT set the DAV:
+     *    displayname property to be the same as any other calendar collection
+     *    at the same URI "level".  When displaying calendar collections to
+     *    users, clients SHOULD check the DAV:displayname property and use that
+     *    value as the name of the calendar.  In the event that the DAV:
+     *    displayname property is empty, the client MAY use the last part of
+     *    the calendar collection URI as the name; however, that path segment
+     *    may be "opaque" and not represent any meaningful human-readable text.
+     *
+     *    If a MKCALENDAR request fails, the server state preceding the request
+     *    MUST be restored.
+     *
+     *    Marshalling:
+     *       If a request body is included, it MUST be a CALDAV:mkcalendar XML
+     *       element.  Instruction processing MUST occur in the order
+     *       instructions are received (i.e., from top to bottom).
+     *       Instructions MUST either all be executed or none executed.  Thus,
+     *       if any error occurs during processing, all executed instructions
+     *       MUST be undone and a proper error result returned.  Instruction
+     *       processing details can be found in the definition of the DAV:set
+     *       instruction in Section 12.13.2 of [RFC2518].
+     *
+     *          
+     *
+     *       If a response body for a successful request is included, it MUST
+     *       be a CALDAV:mkcalendar-response XML element.
+     *
+     *          
+     *
+     *       The response MUST include a Cache-Control:no-cache header.
+     *
+     *    Preconditions:
+     *
+     *       (DAV:resource-must-be-null): A resource MUST NOT exist at the
+     *       Request-URI;
+     *
+     *       (CALDAV:calendar-collection-location-ok): The Request-URI MUST
+     *       identify a location where a calendar collection can be created;
+     *
+     *       (CALDAV:valid-calendar-data): The time zone specified in the
+     *       CALDAV:calendar-timezone property MUST be a valid iCalendar object
+     *       containing a single valid VTIMEZONE component;
+     *
+     *       (DAV:needs-privilege): The DAV:bind privilege MUST be granted to
+     *       the current user on the parent collection of the Request-URI.
+     *
+     *    Postconditions:
+     *
+     *       (CALDAV:initialize-calendar-collection): A new calendar collection
+     *       exists at the Request-URI.  The DAV:resourcetype of the calendar
+     *       collection MUST contain both DAV:collection and CALDAV:calendar
+     *       XML elements.
+     *       
+ * + * @param uri the (partial) URI of the collection to create + * @param properties a set of DAV properties to initialise the collection + * @throws IOException for failures such as network connectivity + * @throws ObjectStoreException when creation fails (i.e. non-2xx HTTP status) + */ + void mkCalendar(String uri, DavPropertySet properties) throws IOException, ObjectStoreException, DavException; + + default List report(String path, CalendarQuery query, DavPropertyName...propertyNames) + throws IOException, ParserConfigurationException { + + DavPropertyNameSet nameSet = new DavPropertyNameSet(); + Arrays.stream(propertyNames).forEach(nameSet::add); + return report(path, query, nameSet); + } + + /** + *
+     *    The REPORT method (defined in Section 3.6 of [RFC3253]) provides an
+     *    extensible mechanism for obtaining information about one or more
+     *    resources.  Unlike the PROPFIND method, which returns the value of
+     *    one or more named properties, the REPORT method can involve more
+     *    complex processing.  REPORT is valuable in cases where the server has
+     *    access to all of the information needed to perform the complex
+     *    request (such as a query), and where it would require multiple
+     *    requests for the client to retrieve the information needed to perform
+     *    the same request.
+     *
+     *    CalDAV servers MUST support the DAV:expand-property REPORT defined in
+     *    Section 3.8 of [RFC3253].
+     *    
+ * + * @param uri + * @param query + * @param propertyNames + * @return + * @throws IOException + * @throws ParserConfigurationException + */ + List report(String uri, CalendarQuery query, DavPropertyNameSet propertyNames) + throws IOException, ParserConfigurationException; + + T report(String path, ReportInfo info, ResponseHandler handler) throws IOException, + ParserConfigurationException; + + /** + * Save calendar data. + * @param uri + * @param calendar + * @throws IOException + */ + void put(String uri, Calendar calendar, String etag) throws IOException, FailedOperationException; + + default Calendar getCalendar(String path) throws IOException { + return get(path, new GetCalendarResource()); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavCollection.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavCollection.java new file mode 100644 index 00000000..70811595 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavCollection.java @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.model.ConstraintViolationException; +import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.property.Uid; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.version.report.ReportInfo; +import org.ical4j.connector.CardCollection; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.ObjectStoreException; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.ical4j.connector.dav.property.CardDavPropertyName; +import org.ical4j.connector.dav.property.DavPropertyBuilder; +import org.ical4j.connector.dav.property.PropertyNameSets; +import org.ical4j.connector.dav.response.GetVCardData; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.apache.jackrabbit.webdav.property.DavPropertyName.DISPLAYNAME; + +/** + * $Id$ + * + * Created on 24/02/2008 + * + * @author Ben + * + */ +public class CardDavCollection extends AbstractDavObjectCollection implements CardCollection { + + /** + * Only {@link CardDavStore} should be calling this, so default modifier is applied. + * + * @param CardDavCalendarStore + * @param id + */ + CardDavCollection(CardDavStore CardDavCalendarStore, String id) { + this(CardDavCalendarStore, id, id, ""); + } + + /** + * Only {@link CardDavStore} should be calling this, so default modifier is applied. + * + * @param cardDavStore + * @param id + * @param displayName + * @param description + */ + CardDavCollection(CardDavStore cardDavStore, String id, String displayName, String description) { + + super(cardDavStore, id); + properties.add(new DavPropertyBuilder<>().name(DISPLAYNAME).value(displayName).build()); + properties.add(new DavPropertyBuilder<>().name(CardDavPropertyName.ADDRESSBOOK_DESCRIPTION).value(description).build()); + } + + CardDavCollection(CardDavStore cardDavStore, String id, DavPropertySet _properties) { + this(cardDavStore, id, id, ""); + this.properties = _properties; + } + + @Override + String getPath() { + return getStore().pathResolver.getCardPath(getId(), getStore().getSessionConfiguration().getWorkspace()); + } + + /** + * Creates this collection on the CardDAV server. + * + * @throws IOException + * @throws ObjectStoreException + */ + final void create() throws IOException, ObjectStoreException { + try { + getStore().getClient().mkCol(getPath(), properties); + } catch (DavException e) { + throw new ObjectStoreException("Failed to create collection", e); + } + } + + /** + * Human-readable name of the collection. + */ + public String getDisplayName() { + try { + return getProperty(DISPLAYNAME, String.class); + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + } + + /** + * Provides a numeric value indicating the maximum size of a resource in octets that the server is willing to accept + * when a calendar object resource is stored in a calendar collection. 0 = no limits. + */ + public long getMaxResourceSize() { + try { + var size = getProperty(CalDavPropertyName.MAX_RESOURCE_SIZE, Long.class); + if (size != null) { + return size; + } + } catch (ObjectStoreException | IOException | DavException e) { + throw new RuntimeException(e); + } + return 0; + } + + /* (non-Javadoc) + * @see org.ical4j.connector.ObjectCollection#getDescription() + */ + public String getDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listObjectUIDs() { + //TODO: extract UIDs from vcards.. + return null; + } + + /* (non-Javadoc) + * @see org.ical4j.connector.ObjectCollection#getComponents() + */ + public Iterable getAll() throws ObjectStoreException { + try { + var info = new ReportInfo(CardDavPropertyName.ADDRESSBOOK_QUERY, 1, + PropertyNameSets.REPORT_CARD); + + return getStore().getClient().report(getPath(), info, new GetVCardData()); + } catch (IOException | ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + + /* (non-Javadoc) + * @see org.ical4j.connector.CardCollection#addCard(net.fortuna.ical4j.vcard.VCard) + */ + public String add(VCard card) throws ObjectStoreException, ConstraintViolationException { + var uid = card.getUid(); + save(card); + return uid.getValue(); + } + + @Override + public Optional get(String uid) { + try { + return Optional.of(getStore().getClient().getVCard(uid)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List removeAll(String... uid) { + return null; + } + + @Override + public Uid[] merge(VCard card) throws ObjectStoreException, ConstraintViolationException, FailedOperationException { + List uids = new ArrayList<>(); + try { + var uidCalendars = card.split(); + for (int i = 0; i < uidCalendars.length; i++) { + add(uidCalendars[i]); + uids.add(uidCalendars[i].getUid()); + } + } catch (ConstraintViolationException cve) { + throw new FailedOperationException("Invalid card format", cve); + } + return uids.toArray(new Uid[0]); + } + + private void save(VCard card) throws ObjectStoreException { + var uid = card.getUid(); + + var path = getPath(); + if (!path.endsWith("/")) { + path = path.concat("/"); + } + try { + getStore().getClient().put(path + uid.getValue() + ".vcf", card, null); + } catch (IOException | FailedOperationException e) { + throw new ObjectStoreException("Error creating calendar on server", e); + } + } + + /** + * {@inheritDoc} + */ + public VCard export() { + throw new UnsupportedOperationException("not implemented"); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavStore.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavStore.java new file mode 100644 index 00000000..1b408d60 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavStore.java @@ -0,0 +1,284 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.vcard.VCard; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.security.SecurityConstants; +import org.apache.jackrabbit.webdav.version.report.ReportInfo; +import org.ical4j.connector.CalendarCollection; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStore; +import org.ical4j.connector.ObjectStoreException; +import org.ical4j.connector.dav.property.BaseDavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.ical4j.connector.dav.property.CardDavPropertyName; +import org.ical4j.connector.dav.property.PropertyNameSets; +import org.ical4j.connector.dav.request.ExpandPropertyQuery; +import org.ical4j.connector.dav.response.GetCardDavCollections; +import org.ical4j.connector.dav.response.GetCollections; +import org.ical4j.connector.dav.response.GetPropertyValue; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; + +/** + * $Id$ + * + * Created on 24/02/2008 + * + * @author Ben + * + */ +public final class CardDavStore extends AbstractDavObjectStore implements + ObjectStore { + + private final String prodId; + private String displayName; + + + /** + * @param prodId application product identifier + * @param url the URL of a CardDav server instance + */ + public CardDavStore( String prodId, URL url ) { + this( prodId, url, null ); + } + + + /** + * @param prodId application product identifier + * @param url the URL of a CardDav server instance + * @param pathResolver the path resolver for the CardDav server type + */ + public CardDavStore(String prodId, URL url, PathResolver pathResolver) { + super(url, pathResolver); + this.prodId = prodId; + } + + /** + * {@inheritDoc} + */ + @Override + public CardDavCollection addCollection(String id) throws ObjectStoreException { + var collection = new CardDavCollection(this, id); + try { + collection.create(); + } catch (IOException e) { + throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); + } + return collection; + } + + @Override + public CardDavCollection addCollection(String id, String workspace) throws ObjectStoreException { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + /** + * {@inheritDoc} + */ + public CardDavCollection addCollection(String id, DavPropertySet properties) throws ObjectStoreException { + var collection = new CardDavCollection(this, id, properties); + try { + collection.create(); + } catch (IOException e) { + throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); + } + return collection; + } + + /** + * {@inheritDoc} + */ + public CardDavCollection getCollection(String id) throws ObjectStoreException, ObjectNotFoundException { + try { + return getClient().propFind(id, PropertyNameSets.PROPFIND_CARD, + new GetCollections(ResourceType.ADRESSBOOK)).entrySet().stream() + .map(e -> new CardDavCollection(this, e.getKey(), e.getValue())) + .collect(Collectors.toList()).get(0); + } catch (IOException e) { + throw new ObjectStoreException(String.format("unable to get collection '%s'", id), e); + } + } + + @Override + public CardDavCollection getCollection(String id, String workspace) throws ObjectStoreException, ObjectNotFoundException { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + /** + * {@inheritDoc} + */ + public CalendarCollection merge(String id, CalendarCollection calendar) { + throw new UnsupportedOperationException("not implemented"); + } + + protected String findAddressBookHomeSet() throws ParserConfigurationException, IOException, DavException { + var propfindPath = pathResolver.getPrincipalPath(getSessionConfiguration().getUser()); + return findAddressBookHomeSet(propfindPath); + } + + /** + * This method try to find the calendar-home-set attribute in the user's DAV principals. The calendar-home-set + * attribute is the URI of the main collection of calendars for the user. + * + * @return the URI for the main calendar collection + * @author Pascal Robert + * @throws ParserConfigurationException + * @throws IOException + * @throws DavException + */ + protected String findAddressBookHomeSet(String propfindUri) throws IOException { + return getClient().propFind(propfindUri, PropertyNameSets.PROPFIND_CARD_HOME, new GetPropertyValue<>()); + } + + /** + * This method will try to find all calendar collections available at the calendar-home-set URI of the user. + * + * @return An array of all available calendar collections + * @author Pascal Robert + * @throws ParserConfigurationException where the parse is not configured correctly + * @throws IOException where a communications error occurs + * @throws DavException where an error occurs calling the DAV method + */ + public List getCollections() throws ObjectStoreException, ObjectNotFoundException { + try { + var calHomeSetPath = findAddressBookHomeSet(); + if (calHomeSetPath == null) { + throw new ObjectNotFoundException("No " + CardDavPropertyName.ADDRESSBOOK_HOME_SET + " attribute found for the user"); + } + return getCollectionsForHomeSet(this, calHomeSetPath); + } catch (DavException | IOException | ParserConfigurationException e) { + throw new ObjectStoreException(e); + } + } + + @Override + public List getCollections(String workspace) throws ObjectStoreException, ObjectNotFoundException { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + protected List getCollectionsForHomeSet(CardDavStore store, + String urlForcalendarHomeSet) throws IOException, DavException { + + return getClient().propFind(urlForcalendarHomeSet, PropertyNameSets.PROPFIND_CARD, + new GetCollections(ResourceType.ADRESSBOOK)).entrySet().stream() + .map(e -> new CardDavCollection(this, e.getKey(), e.getValue())) + .collect(Collectors.toList()); + } + + /** + * Get the list of available delegated collections, Apple's iCal style + * + * @return + * @throws Exception + */ + public List getDelegatedCollections() throws Exception { + + + var methodUri = this.pathResolver.getPrincipalPath(getSessionConfiguration().getUser()); + + var expandPropertyWrite = new ExpandPropertyQuery(ExpandPropertyQuery.Type.PROXY_WRITE_FOR) + .withPropertyName(DavPropertyName.DISPLAYNAME) + .withPropertyName(SecurityConstants.PRINCIPAL_URL) + .withPropertyName(CalDavPropertyName.USER_ADDRESS_SET); + + var expandPropertyRead = new ExpandPropertyQuery(ExpandPropertyQuery.Type.PROXY_READ_FOR) + .withPropertyName(DavPropertyName.DISPLAYNAME) + .withPropertyName(SecurityConstants.PRINCIPAL_URL) + .withPropertyName(CalDavPropertyName.USER_ADDRESS_SET); + + var rinfo = new ReportInfo(BaseDavPropertyName.EXPAND_PROPERTY, 0); + rinfo.setContentElement(expandPropertyWrite.build()); + rinfo.setContentElement(expandPropertyRead.build()); + + return getClient().report(methodUri, rinfo, new GetCardDavCollections()); + } + + /** + * {@inheritDoc} + */ + @Override + public CardDavCollection removeCollection(String id) throws ObjectStoreException, ObjectNotFoundException { + var collection = getCollection(id); + collection.delete(); + return collection; + } + + // public CalendarCollection replace(String id, CalendarCollection calendar) { + // // TODO Auto-generated method stub + // return null; + // } + + + @Override + public List listWorkspaceIds() { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } + + /** + * @return the prodId + */ + final String getProdId() { + return prodId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + /* (non-Javadoc) + * @see org.ical4j.connector.ObjectStore#addCollection(java.lang.String, java.lang.String, java.lang.String, java.lang.String[], net.fortuna.ical4j.model.Calendar) + */ + public CardDavCollection addCollection(String id, String displayName, String description, + String[] supportedComponents, Calendar timezone) throws ObjectStoreException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public CardDavCollection addCollection(String id, String displayName, String description, + String[] supportedComponents, Calendar timezone, + String workspace) throws ObjectStoreException { + throw new UnsupportedOperationException("Workspaces not yet implemented"); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavSupport.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavSupport.java new file mode 100644 index 00000000..bea9a370 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/CardDavSupport.java @@ -0,0 +1,22 @@ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.vcard.VCard; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.dav.response.GetVCardResource; + +import java.io.IOException; + +public interface CardDavSupport extends WebDavSupport { + + /** + * Save card data. + * @param uri + * @param card + * @throws IOException + */ + void put(String uri, VCard card, String etag) throws IOException, FailedOperationException; + + default VCard getVCard(String path) throws IOException { + return get(path, new GetVCardResource()); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavClientConfiguration.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavClientConfiguration.java new file mode 100644 index 00000000..77daefe6 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavClientConfiguration.java @@ -0,0 +1,40 @@ +package org.ical4j.connector.dav; + +import java.util.HashMap; +import java.util.Map; + +public class DavClientConfiguration { + + private boolean preemptiveAuth; + + private boolean followRedirects; + + private Map defaultHeaders = new HashMap<>(); + + public DavClientConfiguration withPreemptiveAuth(boolean preemptiveAuth) { + this.preemptiveAuth = preemptiveAuth; + return this; + } + + public DavClientConfiguration withFollowRedirects(boolean followRedirects) { + this.followRedirects = followRedirects; + return this; + } + + public DavClientConfiguration withDefaultHeader(String name, String value) { + this.defaultHeaders.put(name, value); + return this; + } + + public boolean isPreemptiveAuth() { + return preemptiveAuth; + } + + public boolean isFollowRedirects() { + return followRedirects; + } + + public Map getDefaultHeaders() { + return new HashMap<>(defaultHeaders); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavClientFactory.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavClientFactory.java new file mode 100644 index 00000000..2a4eb05e --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavClientFactory.java @@ -0,0 +1,69 @@ +package org.ical4j.connector.dav; + +import org.apache.http.client.CredentialsProvider; + +import java.net.MalformedURLException; +import java.net.URL; + +public class DavClientFactory { + + private DavClientConfiguration clientConfiguration; + + private CredentialsProvider credentialsProvider; + + public DavClientFactory() { + clientConfiguration = new DavClientConfiguration(); + } + + public DavClientFactory withPreemptiveAuth(boolean preemptiveAuth) { + this.clientConfiguration = clientConfiguration.withPreemptiveAuth(preemptiveAuth); + return this; + } + + public DavClientFactory withFollowRedirects(boolean followRedirects) { + this.clientConfiguration = clientConfiguration.withFollowRedirects(followRedirects); + return this; + } + + public DavClientFactory withDefaultHeader(String name, String value) { + this.clientConfiguration = clientConfiguration.withDefaultHeader(name, value); + return this; + } + + public DavClientFactory withCredentialsProvider(CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Create a new client instance. Note that if a path is specified it should include a trailing + * forward slash. + * @param url the URL of the DAV repository + * @return a new client instance + */ + public DefaultDavClient newInstance(URL url) { + var client = new DefaultDavClient(url, clientConfiguration); + if (credentialsProvider != null) { + client.begin(credentialsProvider); + } else { + client.begin(); + } + return client; + } + + /** + * Create a new client instance. Note that if a path is specified it should include a trailing + * forward slash. + * @param url the URL of the DAV repository + * @return a new client instance + */ + public DefaultDavClient newInstance(String url) throws MalformedURLException { + var client = new DefaultDavClient(url, clientConfiguration); + if (credentialsProvider != null) { + client.begin(credentialsProvider); + } else { + client.begin(); + } + return client; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavSessionConfiguration.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavSessionConfiguration.java new file mode 100644 index 00000000..918d21af --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DavSessionConfiguration.java @@ -0,0 +1,61 @@ +package org.ical4j.connector.dav; + +import org.apache.http.client.CredentialsProvider; + +public class DavSessionConfiguration { + + private String user; + + private char[] password; + + private String bearerAuth; + + private CredentialsProvider credentialsProvider; + + private String workspace; + + public DavSessionConfiguration withUser(String user) { + this.user = user; + return this; + } + + public DavSessionConfiguration withPassword(char[] password) { + this.password = password; + return this; + } + + public DavSessionConfiguration withBearerAuth(String bearerAuth) { + this.bearerAuth = bearerAuth; + return this; + } + + public DavSessionConfiguration withCredentialsProvider(CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + public DavSessionConfiguration withWorkspace(String workspace) { + this.workspace = workspace; + return this; + } + + public String getUser() { + return user; + } + + public char[] getPassword() { + return password; + } + + public String getBearerAuth() { + return bearerAuth; + } + + public CredentialsProvider getCredentialsProvider() { + return credentialsProvider; + } + + public String getWorkspace() { + return workspace; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DefaultDavClient.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DefaultDavClient.java new file mode 100644 index 00000000..45a28dad --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/DefaultDavClient.java @@ -0,0 +1,419 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.property.Attendee; +import net.fortuna.ical4j.model.property.Organizer; +import net.fortuna.ical4j.vcard.VCard; +import org.apache.commons.io.output.WriterOutputStream; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.client.protocol.RequestDefaultHeaders; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.LaxRedirectStrategy; +import org.apache.http.message.BasicHeader; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.client.methods.*; +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.property.PropEntry; +import org.apache.jackrabbit.webdav.version.report.ReportInfo; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.ObjectStoreException; +import org.ical4j.connector.dav.method.*; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.ical4j.connector.dav.property.PropertyNameSets; +import org.ical4j.connector.dav.request.CalendarQuery; +import org.ical4j.connector.dav.request.MkCalendarEntity; +import org.ical4j.connector.dav.request.MkColEntity; +import org.ical4j.connector.dav.response.*; +import org.jetbrains.annotations.NotNull; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class DefaultDavClient implements CalDavSupport, CardDavSupport { + + /** + * The HTTP client configuration. + */ + private final HttpHost hostConfiguration; + + private final String repositoryPath; + + private final DavClientConfiguration clientConfiguration; + + /** + * The underlying HTTP client. + */ + protected HttpClient httpClient; + + protected HttpClientContext httpClientContext; + + /** + * Create a disconnected DAV client instance. + * @param href a URL string representing a DAV host location + * @param clientConfiguration configuration parameters specific to a DAV client + */ + DefaultDavClient(@NotNull String href, DavClientConfiguration clientConfiguration) throws MalformedURLException { + this(URI.create(href).toURL(), clientConfiguration); + } + + /** + * Create a disconnected DAV client instance. + * @param href a URL string representing a DAV host location + * @param clientConfiguration configuration parameters specific to a DAV client + */ + DefaultDavClient(@NotNull URL href, DavClientConfiguration clientConfiguration) { + + this.hostConfiguration = new HttpHost(href.getHost(), href.getPort(), href.getProtocol()); + this.repositoryPath = href.getPath().isEmpty() ? "/" : href.getPath(); + this.clientConfiguration = clientConfiguration; + } + + void begin() { + begin((CredentialsProvider) null); + } + + public List begin(String bearerAuth) throws IOException, FailedOperationException { + clientConfiguration.withDefaultHeader("Authorization", "Bearer " + bearerAuth); + begin(); + return getSupportedFeatures(); + } + + public List begin(String username, char[] password) throws IOException, FailedOperationException { + var credentials = new UsernamePasswordCredentials(username, new String(password)); + + var credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(new AuthScope(hostConfiguration), credentials); + begin(credentialsProvider); + + return getSupportedFeatures(); + } + + void begin(CredentialsProvider credentialsProvider) { + + var builder = HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider); + + Collection
defaultHeaders = clientConfiguration.getDefaultHeaders().entrySet().stream() + .map(e -> new BasicHeader(e.getKey(), e.getValue())).collect(Collectors.toList()); + builder.addInterceptorLast(new RequestDefaultHeaders(defaultHeaders)); + + if (clientConfiguration.isFollowRedirects()) { + builder.setRedirectStrategy(new LaxRedirectStrategy()); + } + httpClient = builder.build(); + httpClientContext = HttpClientContext.create(); + + if (clientConfiguration.isPreemptiveAuth()) { + AuthCache authCache = new BasicAuthCache(); + authCache.put(hostConfiguration, new BasicScheme()); + httpClientContext.setAuthCache(authCache); + } + } + + public HttpHost getHostConfiguration() { + return hostConfiguration; + } + + public String getRepositoryPath() { + return repositoryPath; + } + + public DavClientConfiguration getClientConfiguration() { + return clientConfiguration; + } + + public List getSupportedFeatures() throws IOException { + var aGet = new HttpPropfind(repositoryPath, DavConstants.PROPFIND_BY_PROPERTY, + PropertyNameSets.PROPFIND_SUPPORTED_FEATURES, 0); + + var builder = aGet.getConfig() == null ? RequestConfig.custom() : RequestConfig.copy(aGet.getConfig()); + builder.setAuthenticationEnabled(true); + // Added to support iCal Server, who don't support Basic auth at all, + // only Kerberos and Digest + List authPrefs = new ArrayList<>(2); + authPrefs.add(AuthSchemes.DIGEST); + authPrefs.add(AuthSchemes.BASIC); + builder.setTargetPreferredAuthSchemes(authPrefs); + + var config = builder.build(); + aGet.setConfig(config); + return execute(aGet, new GetSupportedFeatures()); + } + + @Override + public void mkCalendar(String path, DavPropertySet properties) throws IOException, ObjectStoreException, DavException { + var mkCalendarMethod = new MkCalendar(resolvePath(path)); + mkCalendarMethod.setEntity(XmlEntity.create(new MkCalendarEntity().withProperties(properties))); + var httpResponse = execute(mkCalendarMethod); + if (!mkCalendarMethod.succeeded(httpResponse)) { + throw new ObjectStoreException(httpResponse.getStatusLine().getStatusCode() + ": " + + httpResponse.getStatusLine().getReasonPhrase()); + } + } + + @Override + public void mkCol(String path, DavPropertySet properties) throws ObjectStoreException, DavException, IOException { + var mkcolMethod = new HttpMkcol(resolvePath(path)); + mkcolMethod.setEntity(XmlEntity.create(new MkColEntity().withProperties(properties))); + var httpResponse = execute(mkcolMethod); + if (!mkcolMethod.succeeded(httpResponse)) { + throw new ObjectStoreException(httpResponse.getStatusLine().getStatusCode() + ": " + + httpResponse.getStatusLine().getReasonPhrase()); + } + } + + @Override + public List propFind(String path, DavPropertyNameSet propertyNames) throws IOException { + return propFind(path, propertyNames, new GetResourceProperties()); + } + + @Override + public T propFind(String path, DavPropertyNameSet propertyNames, ResponseHandler handler) + throws IOException { + var aGet = new HttpPropfind(resolvePath(path), propertyNames, 0); + return execute(aGet, handler); + } + + /** + * + * @param path + * @param propertyNames + * @param resourceTypes + * @return + * @throws IOException + * @deprecated use {@link WebDavSupport#propFind(String, DavPropertyNameSet, ResponseHandler)} instead + */ + @Deprecated + public Map propFindResources(String path, DavPropertyNameSet propertyNames, + ResourceType...resourceTypes) throws IOException { + return propFind(path, propertyNames, new GetCollections(resourceTypes)); + } + + @Override + public List propFindType(String path, int type) throws IOException { + var aGet = new HttpPropfind(resolvePath(path), type, 1); + +// RequestConfig config = RequestConfig.custom().setAuthenticationEnabled(true).build(); +// aGet.setConfig(config); + return execute(aGet, new GetResourceProperties()); + } + + @Override + public List report(String path, CalendarQuery query, DavPropertyNameSet propertyNames) + throws IOException, ParserConfigurationException { + + var info = new ReportInfo(CalDavPropertyName.CALENDAR_QUERY, 1, propertyNames); + info.setContentElement(query.build()); + + return report(path, info, new GetResourceProperties()); + } + + @Override + public T report(String path, ReportInfo info, ResponseHandler handler) throws IOException, + ParserConfigurationException { + + var method = new HttpReport(resolvePath(path), info); + return execute(method, handler); + } + + @Override + public T get(String path, ResponseHandler handler) throws IOException { + var httpGet = new HttpGet(resolvePath(path)); + return execute(httpGet, handler); + } + + @Override + public T head(String path, ResponseHandler handler) throws IOException { + var httpHead = new HttpHead(resolvePath(path)); + return execute(httpHead, handler); + } + + @Override + public void put(String path, Calendar calendar, String etag) throws IOException, FailedOperationException { + var httpPut = new PutCalendar(resolvePath(path)); + if (etag != null) { + httpPut.setEtag(etag); + } + httpPut.setCalendar(calendar); + var httpResponse = execute(httpPut); + if (!httpPut.succeeded(httpResponse)) { + var w = new StringWriter(); + httpResponse.getEntity().writeTo(WriterOutputStream.builder().setWriter(w).get()); + throw new FailedOperationException( + "Error creating calendar on server: " + httpResponse.getStatusLine() + "-" + w); + } + } + + @Override + public void put(String path, VCard card, String etag) throws IOException, FailedOperationException { + var httpPut = new PutVCard(resolvePath(path)); + httpPut.setEtag(etag); + httpPut.setVCard(card); + var httpResponse = execute(httpPut); + if (!httpPut.succeeded(httpResponse)) { + throw new FailedOperationException( + "Error creating card on server: " + httpResponse.getStatusLine()); + } + } + + @Override + public void copy(String src, String dest) throws IOException { + var method = new HttpCopy(resolvePath(src), resolvePath(dest), true, false); + execute(method, response -> { + try { + method.checkSuccess(response); + } catch (DavException e) { + throw new RuntimeException(e); + } + return true; + }); + } + + @Override + public void move(String src, String dest) throws IOException { + var method = new HttpMove(resolvePath(src), resolvePath(dest), true); + execute(method, response -> { + try { + method.checkSuccess(response); + } catch (DavException e) { + throw new RuntimeException(e); + } + return true; + }); + } + + @Override + public void delete(String path) throws IOException, DavException { + var method = new HttpDelete(resolvePath(path)); + execute(method, response -> { + try { + method.checkSuccess(response); + } catch (DavException e) { + throw new RuntimeException(e); + } + return true; + }); + } + + public List freeBusy(String path, Calendar query, Organizer organizer) throws IOException { + var freeBusy = new FreeBusy(resolvePath(path), organizer); + freeBusy.setQuery(query); + return execute(freeBusy, new GetFreeBusyData()); + } + + @Override + public MultiStatusResponse propPatch(String path, List changeList) throws IOException { + var method = new HttpProppatch(resolvePath(path), changeList); + return execute(method, response -> { + try { + method.checkSuccess(response); + return method.getResponseBodyAsMultiStatus(response).getResponses()[0]; + } catch (DavException e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void post() { + + } + + @Override + public void lock(String path) { + + } + + @Override + public void unlock(String path) { + + } + + public List findPrincipals(String path, PrincipalPropertySearchInfo info) throws IOException { + var principalPropertySearch = new PrincipalPropertySearch(resolvePath(path), info); + return execute(principalPropertySearch, new GetPrincipals()); + } + + private String resolvePath(String path) { + if (path == null) { + return repositoryPath; + } else if (path.startsWith("/")) { + return (repositoryPath + path).replaceAll("/+", "/"); + } else { + return (repositoryPath + "/" + path).replaceAll("/+", "/"); + } + } + + private HttpResponse execute(BaseDavRequest method) throws IOException, DavException { + var response = httpClient.execute(hostConfiguration, method, httpClientContext); + method.checkSuccess(response); + return response; + } + + private HttpResponse execute(HttpRequest method) throws IOException { + return httpClient.execute(hostConfiguration, method, httpClientContext); + } + + private T execute(HttpRequest method, ResponseHandler handler) throws IOException { + return httpClient.execute(hostConfiguration, method, handler, httpClientContext); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/PathResolver.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/PathResolver.java new file mode 100644 index 00000000..7d930272 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/PathResolver.java @@ -0,0 +1,197 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Implementations resolve host path elements. + * + * @author fortuna + * + * Created on: 02/04/2009 + * + * $Id$ + */ +public interface PathResolver { + + /** + * Return the principal collection path for a DAV server implementation. + * @param calendarId a calendar id + * @return a string representing the path to the principal collection for a calendar + */ + String getPrincipalPath(String calendarId); + + /** + * Return the resource path (e.g. calendar collection) for a DAV server implementation. + * @param resourcePath a path component representing a resource + * @return a string representing the repository path to the resource + */ + String getCalendarPath(String resourcePath, String wspPath); + + String getCardPath(String resourcePath, String wspPath); + + String getResourceName(String repositoryPath, String wspPath); + + /** + * Default path resolver configurations for some popular CalDAV/CardDAV implementations. + * + * Each configuration provides a pattern for the follow path types: + * - principals, or who has access to the calendar/card resources + * - calendar collections, containers for icalendar objects + * - card collections, containers for vcard objects + * + * A configuration also provides a general path pattern that is used to extract the component paths + * from a full repository path, such that: + * - pathPattern(principalPath(resource))[resource] == resource + * - pathPattern(calendarPath(resource))[resource] == resource + * - pathPattern(cardPath(resource))[resource] == resource + */ + enum Defaults implements PathResolver { + + CHANDLER( "/%s/", "/users/%s", "", + "/users/(?\\w+)/?"), + + RADICALE( "/%s", + "/%2$s/%1$s", + "/%2$s/%1$s", + "\\/(?[\\w]+)\\/(?[\\w]+)"), + + BAIKAL( "/principals/%s", + "/calendars/%2$s/%1$s", + "/addressbooks/%2$s/%1$s", + "\\/(?(principals|calendars|addressbooks))\\/((?[\\w]+)\\/)?(?[\\w]+)"), + + CGP("/", "/", "","/(?\\w+)/?"), + + KMS("/", "/", "","/(?\\w+)/?"), + + ZIMBRA("/principals/users/%s/", "/dav/%s/", "", + "/(?\\w+)/?"), + + ICAL_SERVER( "/principals/users/%s/", "/dav/%s/", "", + "/(?\\w+)/?"), + + CALENDAR_SERVER( "/%s/", "/dav/%s", "","/(?\\w+)/?"), + + GCAL( "/%s/user", "/%s/events", "", + "/(?\\w+)/events/?"), + + SOGO("/%s/", "/%s/", "","/(?\\w+)/?"), + + DAVICAL("/%s/", "/%s/", "","/(?\\w+)/?"), + + BEDEWORK("/principals/users/%s/", "/ucaldav/users/%s", "", + "/users/(?\\w+)/?"), + + ORACLE_CS("/principals/%s/", "/home/%s/", "", + "/home/(?\\w+)/?"), + + GENERIC("/%s", "/%s", "","/(?\\w+)"); + + private final String principalPath; + + private final String calendarPath; + + private final String cardPath; + + private final Pattern pathPattern; + + Defaults(String principalPath, String calendarPath, String cardPath, String pathPattern) { + this.principalPath = principalPath; + this.calendarPath = calendarPath; + this.cardPath = cardPath; + this.pathPattern = Pattern.compile(pathPattern); + } + + /** + * Resolves the path component for a user's calendar store URL. + * + * @param resourceName a username + * @return the user path for a server implementation + */ + @Override + public String getCalendarPath(String resourceName, String wspPath) { + if (wspPath != null && !wspPath.isEmpty()) { + return String.format("/%s", String.format(calendarPath, resourceName, wspPath)) + .replaceAll("/+", "/"); + } else { + return String.format("/%s", String.format(calendarPath, resourceName, "")) + .replaceAll("/+", "/"); + } + } + + @Override + public String getCardPath(String resourcePath, String wspPath) { + if (wspPath != null && !wspPath.isEmpty()) { + return String.format("/%s", String.format(cardPath, resourcePath, wspPath)) + .replaceAll("/+", "/"); + } else { + return String.format("/%s", String.format(cardPath, resourcePath, "")) + .replaceAll("/+", "/"); + } + } + + /** + * Reverse resolution of a calendar identifier from a repository path. + * @param repositoryPath a repository path + * @return a calendar id + */ + @Override + public String getResourceName(String repositoryPath, String wspPath) { + Matcher matcher = pathPattern.matcher(repositoryPath); +// if (matcher.matches() && matcher.groupCount() > 0) { +// if (wspPath != null && !wspPath.isEmpty()) { +// return String.format("%s/%s", wspPath, matcher.group(1)); +// } else { +// return matcher.group(1); +// } +// } + if (matcher.matches()) { + return matcher.group("resource"); + } + return null; + } + + /** + * Resolves the path component for a principal URL. + * + * @param calendarId a username + * @return the principal path for a server implementation + */ + @Override + public String getPrincipalPath(String calendarId) { + return String.format(principalPath, calendarId); + } + } +} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/enums/ResourceType.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/ResourceType.java similarity index 94% rename from src/main/java/net/fortuna/ical4j/connector/dav/enums/ResourceType.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/ResourceType.java index 94367958..90f2efd7 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/enums/ResourceType.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/ResourceType.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.enums; +package org.ical4j.connector.dav; import java.util.ArrayList; import java.util.HashSet; @@ -55,7 +55,7 @@ public enum ResourceType { private static Set index = new HashSet(); static { - for (ResourceType supportedFeature : ResourceType.values()) { + for (var supportedFeature : ResourceType.values()) { index.add(supportedFeature.description()); } } @@ -73,7 +73,7 @@ public static ArrayList descriptions() { } public static ResourceType findByDescription(String value) { - for (ResourceType feature : values()) { + for (var feature : values()) { if (feature.description().equals(value)) { return feature; } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/ScheduleResponse.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/ScheduleResponse.java similarity index 81% rename from src/main/java/net/fortuna/ical4j/connector/dav/ScheduleResponse.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/ScheduleResponse.java index af53c7f4..5014672b 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/ScheduleResponse.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/ScheduleResponse.java @@ -29,17 +29,16 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav; +package org.ical4j.connector.dav; -import net.fortuna.ical4j.connector.dav.property.CalDavPropertyName; import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.Calendar; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; import org.w3c.dom.CDATASection; import org.w3c.dom.Element; -import org.w3c.dom.NodeList; import org.w3c.dom.Text; import java.io.IOException; @@ -79,43 +78,43 @@ public class ScheduleResponse { * ]]> OK */ public ScheduleResponse(Element responseNode) throws IOException, ParserException { - NodeList recipients = responseNode.getElementsByTagNameNS(CalDavConstants.CALDAV_NAMESPACE.getURI(), + var recipients = responseNode.getElementsByTagNameNS(CalDavPropertyName.NAMESPACE.getURI(), CalDavPropertyName.RECIPIENT.getName()); - NodeList status = responseNode.getElementsByTagNameNS(CalDavConstants.CALDAV_NAMESPACE.getURI(), + var status = responseNode.getElementsByTagNameNS(CalDavPropertyName.NAMESPACE.getURI(), CalDavPropertyName.REQUEST_STATUS.getName()); - NodeList calendars = responseNode.getElementsByTagNameNS(CalDavConstants.CALDAV_NAMESPACE.getURI(), + var calendars = responseNode.getElementsByTagNameNS(CalDavPropertyName.NAMESPACE.getURI(), CalDavPropertyName.CALENDAR_DATA.getName()); for (int nodesIndex = 0; nodesIndex < calendars.getLength(); nodesIndex++) { - Element node = (Element) calendars.item(nodesIndex); + var node = (Element) calendars.item(nodesIndex); if (node.getFirstChild() != null) { - CalendarBuilder builder = new CalendarBuilder(); + var builder = new CalendarBuilder(); if (node.getFirstChild() instanceof CDATASection) { - CDATASection calData = (CDATASection) node.getFirstChild(); - StringReader sin = new StringReader(calData.getData()); + var calData = (CDATASection) node.getFirstChild(); + var sin = new StringReader(calData.getData()); this.calendarData = builder.build(sin); } // KMS don't return CDATA, go figure if (node.getFirstChild() instanceof Text) { - StringReader sin = new StringReader(((Text) node.getFirstChild()).getTextContent()); + var sin = new StringReader(((Text) node.getFirstChild()).getTextContent()); this.calendarData = builder.build(sin); } } } for (int nodesIndex = 0; nodesIndex < status.getLength(); nodesIndex++) { - Element node = (Element) status.item(nodesIndex); - String fullStatus = ((Text) node.getFirstChild()).getTextContent(); - String[] split = fullStatus.split(";"); + var node = (Element) status.item(nodesIndex); + var fullStatus = ((Text) node.getFirstChild()).getTextContent(); + var split = fullStatus.split(";"); if (split.length == 2) { - this.requestStatusCode = new Float(split[0]).floatValue(); + this.requestStatusCode = Float.parseFloat(split[0]); this.requestStatusMessage = split[1]; } } for (int nodesIndex = 0; nodesIndex < recipients.getLength(); nodesIndex++) { - Element node = (Element) recipients.item(nodesIndex); - NodeList childs = node.getElementsByTagNameNS(DavConstants.NAMESPACE.getURI(), DavPropertyName.XML_HREF); + var node = (Element) recipients.item(nodesIndex); + var childs = node.getElementsByTagNameNS(DavConstants.NAMESPACE.getURI(), DavPropertyName.XML_HREF); if ((childs != null) && (childs.item(0) != null) && (childs.item(0).getFirstChild() != null)) { this.recipient = childs.item(0).getFirstChild().getTextContent(); } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/enums/SupportedFeature.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/SupportedFeature.java similarity index 94% rename from src/main/java/net/fortuna/ical4j/connector/dav/enums/SupportedFeature.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/SupportedFeature.java index a248db4c..ace7c647 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/enums/SupportedFeature.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/SupportedFeature.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.enums; +package org.ical4j.connector.dav; import java.util.ArrayList; import java.util.HashSet; @@ -59,7 +59,7 @@ public enum SupportedFeature { private static Set index = new HashSet(); static { - for (SupportedFeature supportedFeature : SupportedFeature.values()) { + for (var supportedFeature : SupportedFeature.values()) { index.add(supportedFeature.description()); } } @@ -77,7 +77,7 @@ public static ArrayList descriptions() { } public static SupportedFeature findByDescription(String value) { - for (SupportedFeature feature : values()) { + for (var feature : values()) { if (feature.description().equals(value)) { return feature; } diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/WebDavSupport.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/WebDavSupport.java new file mode 100644 index 00000000..212c124a --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/WebDavSupport.java @@ -0,0 +1,379 @@ +package org.ical4j.connector.dav; + +import org.apache.http.client.ResponseHandler; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.property.*; +import org.ical4j.connector.ObjectStoreException; +import org.ical4j.connector.dav.response.ResourceProps; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public interface WebDavSupport { + + /** + *
+     *    The PROPFIND method retrieves properties defined on the resource
+     *    identified by the Request-URI, if the resource does not have any
+     *    internal members, or on the resource identified by the Request-URI
+     *    and potentially its member resources, if the resource is a collection
+     *    that has internal member URLs.  All DAV-compliant resources MUST
+     *    support the PROPFIND method and the propfind XML element
+     *    (Section 14.20) along with all XML elements defined for use with that
+     *    element.
+     *
+     *    A client MUST submit a Depth header with a value of "0", "1", or
+     *    "infinity" with a PROPFIND request.  Servers MUST support "0" and "1"
+     *    depth requests on WebDAV-compliant resources and SHOULD support
+     *    "infinity" requests.  In practice, support for infinite-depth
+     *    requests MAY be disabled, due to the performance and security
+     *    concerns associated with this behavior.  Servers SHOULD treat a
+     *    request without a Depth header as if a "Depth: infinity" header was
+     *    included.
+     *
+     *    A client may submit a 'propfind' XML element in the body of the
+     *    request method describing what information is being requested.  It is
+     *    possible to:
+     *
+     *    o  Request particular property values, by naming the properties
+     *       desired within the 'prop' element (the ordering of properties in
+     *       here MAY be ignored by the server),
+     *
+     *    o  Request property values for those properties defined in this
+     *       specification (at a minimum) plus dead properties, by using the
+     *       'allprop' element (the 'include' element can be used with
+     *       'allprop' to instruct the server to also include additional live
+     *       properties that may not have been returned otherwise),
+     *
+     *    o  Request a list of names of all the properties defined on the
+     *       resource, by using the 'propname' element.
+     *
+     *    A client may choose not to submit a request body.  An empty PROPFIND
+     *    request body MUST be treated as if it were an 'allprop' request.
+     *
+     *    Note that 'allprop' does not return values for all live properties.
+     *    WebDAV servers increasingly have expensively-calculated or lengthy
+     *    properties (see [RFC3253] and [RFC3744]) and do not return all
+     *    properties already.  Instead, WebDAV clients can use propname
+     *    requests to discover what live properties exist, and request named
+     *    properties when retrieving values.  For a live property defined
+     *    elsewhere, that definition can specify whether or not that live
+     *    property would be returned in 'allprop' requests.
+     *
+     *    All servers MUST support returning a response of content type text/
+     *    xml or application/xml that contains a multistatus XML element that
+     *    describes the results of the attempts to retrieve the various
+     *    properties.
+     *
+     *    If there is an error retrieving a property, then a proper error
+     *    result MUST be included in the response.  A request to retrieve the
+     *    value of a property that does not exist is an error and MUST be noted
+     *    with a 'response' XML element that contains a 404 (Not Found) status
+     *    value.
+     *
+     *    Consequently, the 'multistatus' XML element for a collection resource
+     *    MUST include a 'response' XML element for each member URL of the
+     *    collection, to whatever depth was requested.  It SHOULD NOT include
+     *    any 'response' elements for resources that are not WebDAV-compliant.
+     *    Each 'response' element MUST contain an 'href' element that contains
+     *    the URL of the resource on which the properties in the prop XML
+     *    element are defined.  Results for a PROPFIND on a collection resource
+     *    are returned as a flat list whose order of entries is not
+     *    significant.  Note that a resource may have only one value for a
+     *    property of a given name, so the property may only show up once in
+     *    PROPFIND responses.
+     *
+     *    Properties may be subject to access control.  In the case of
+     *    'allprop' and 'propname' requests, if a principal does not have the
+     *    right to know whether a particular property exists, then the property
+     *    MAY be silently excluded from the response.
+     *
+     *    Some PROPFIND results MAY be cached, with care, as there is no cache
+     *    validation mechanism for most properties.  This method is both safe
+     *    and idempotent (see Section 9.1 of [RFC2616]).
+     *    
+ * + * @param path a resource URI + * @param propertyNames the set of properties to return + * @return a property set matching the requested property names + * @throws IOException + */ + List propFind(String path, DavPropertyNameSet propertyNames) throws IOException; + + /** + * + * @param path + * @param propertyNames + * @return + * @throws IOException + */ + default List propFind(String path, DavPropertyName... propertyNames) throws IOException { + DavPropertyNameSet nameSet = new DavPropertyNameSet(); + Arrays.stream(propertyNames).forEach(nameSet::add); + return propFind(path, nameSet); + } + + T propFind(String path, DavPropertyNameSet propertyNames, ResponseHandler handler) throws IOException; + + default List propFindAll(String path) throws IOException { + return propFindType(path, DavPropertyName.PROPFIND_ALL_PROP); + } + + /** + * @param path + * @param type + * @return + * @throws IOException + */ + List propFindType(String path, int type) throws IOException; + + /** + *
+     *    The PROPPATCH method processes instructions specified in the request
+     *    body to set and/or remove properties defined on the resource
+     *    identified by the Request-URI.
+     *
+     *    All DAV-compliant resources MUST support the PROPPATCH method and
+     *    MUST process instructions that are specified using the
+     *    propertyupdate, set, and remove XML elements.  Execution of the
+     *    directives in this method is, of course, subject to access control
+     *    constraints.  DAV-compliant resources SHOULD support the setting of
+     *    arbitrary dead properties.
+     *
+     *    The request message body of a PROPPATCH method MUST contain the
+     *    propertyupdate XML element.
+     *
+     *    Servers MUST process PROPPATCH instructions in document order (an
+     *    exception to the normal rule that ordering is irrelevant).
+     *    Instructions MUST either all be executed or none executed.  Thus, if
+     *    any error occurs during processing, all executed instructions MUST be
+     *    undone and a proper error result returned.  Instruction processing
+     *    details can be found in the definition of the set and remove
+     *    instructions in Sections 14.23 and 14.26.
+     *
+     *    If a server attempts to make any of the property changes in a
+     *    PROPPATCH request (i.e., the request is not rejected for high-level
+     *    errors before processing the body), the response MUST be a Multi-
+     *    Status response as described in Section 9.2.1.
+     *
+     *    This method is idempotent, but not safe (see Section 9.1 of
+     *    [RFC2616]).  Responses to this method MUST NOT be cached.
+     *    
+ * @return + */ + MultiStatusResponse propPatch(String path, List changeList) throws IOException; + + default MultiStatusResponse propPatchSet(String path, DavPropertySet properties) throws IOException { + return propPatch(path, new ArrayList<>(properties.getContent())); + } + + default MultiStatusResponse propPatchSet(String path, DavProperty... properties) throws IOException { + DavPropertySet propertySet = new DavPropertySet(); + Arrays.stream(properties).forEach(propertySet::add); + return propPatchSet(path, propertySet); + } + + default MultiStatusResponse propPatchRemove(String path, DavPropertyNameSet propertyNames) throws IOException { + return propPatch(path, new ArrayList<>(propertyNames.getContent())); + } + + default MultiStatusResponse propPatchRemove(String path, DavPropertyName... propertyNames) throws IOException { + DavPropertyNameSet nameSet = new DavPropertyNameSet(); + Arrays.stream(propertyNames).forEach(nameSet::add); + return propPatchRemove(path, nameSet); + } + + /** + *
+     *    MKCOL creates a new collection resource at the location specified by
+     *    the Request-URI.  If the Request-URI is already mapped to a resource,
+     *    then the MKCOL MUST fail.  During MKCOL processing, a server MUST
+     *    make the Request-URI an internal member of its parent collection,
+     *    unless the Request-URI is "/".  If no such ancestor exists, the
+     *    method MUST fail.  When the MKCOL operation creates a new collection
+     *    resource, all ancestors MUST already exist, or the method MUST fail
+     *    with a 409 (Conflict) status code.  For example, if a request to
+     *    create collection /a/b/c/d/ is made, and /a/b/c/ does not exist, the
+     *    request must fail.
+     *
+     *    When MKCOL is invoked without a request body, the newly created
+     *    collection SHOULD have no members.
+     *
+     *
+     *    A MKCOL request message may contain a message body.  The precise
+     *    behavior of a MKCOL request when the body is present is undefined,
+     *    but limited to creating collections, members of a collection, bodies
+     *    of members, and properties on the collections or members.  If the
+     *    server receives a MKCOL request entity type it does not support or
+     *    understand, it MUST respond with a 415 (Unsupported Media Type)
+     *    status code.  If the server decides to reject the request based on
+     *    the presence of an entity or the type of an entity, it should use the
+     *    415 (Unsupported Media Type) status code.
+     *
+     *    This method is idempotent, but not safe (see Section 9.1 of
+     *    [RFC2616]).  Responses to this method MUST NOT be cached.
+     *    
+ */ + void mkCol(String path, DavPropertySet properties) throws ObjectStoreException, DavException, IOException; + + /** + *
+     *    The semantics of GET are unchanged when applied to a collection,
+     *    since GET is defined as, "retrieve whatever information (in the form
+     *    of an entity) is identified by the Request-URI" [RFC2616].  GET, when
+     *    applied to a collection, may return the contents of an "index.html"
+     *    resource, a human-readable view of the contents of the collection, or
+     *    something else altogether.  Hence, it is possible that the result of
+     *    a GET on a collection will bear no correlation to the membership of
+     *    the collection.
+     *
+     *    Similarly, since the definition of HEAD is a GET without a response
+     *    message body, the semantics of HEAD are unmodified when applied to
+     *    collection resources.
+     *    
+ * + * @param + * @return + */ + T get(String path, ResponseHandler handler) throws IOException; + + T head(String path, ResponseHandler handler) throws IOException; + + /** + *
+     *    Since by definition the actual function performed by POST is
+     *    determined by the server and often depends on the particular
+     *    resource, the behavior of POST when applied to collections cannot be
+     *    meaningfully modified because it is largely undefined.  Thus, the
+     *    semantics of POST are unmodified when applied to a collection.
+     *    
+ */ + void post(); + + /** + *
+     *    DELETE is defined in [RFC2616], Section 9.7, to "delete the resource
+     *    identified by the Request-URI".  However, WebDAV changes some DELETE
+     *    handling requirements.
+     *
+     *    A server processing a successful DELETE request:
+     *
+     *       MUST destroy locks rooted on the deleted resource
+     *
+     *       MUST remove the mapping from the Request-URI to any resource.
+     *
+     *    Thus, after a successful DELETE operation (and in the absence of
+     *    other actions), a subsequent GET/HEAD/PROPFIND request to the target
+     *    Request-URI MUST return 404 (Not Found).
+     *    
+ * + * + * @param path + * @throws IOException + * @throws DavException + */ + void delete(String path) throws IOException, DavException; + + /** + *
+     *    The COPY method creates a duplicate of the source resource identified
+     *    by the Request-URI, in the destination resource identified by the URI
+     *    in the Destination header.  The Destination header MUST be present.
+     *    The exact behavior of the COPY method depends on the type of the
+     *    source resource.
+     *
+     *    All WebDAV-compliant resources MUST support the COPY method.
+     *    However, support for the COPY method does not guarantee the ability
+     *    to copy a resource.  For example, separate programs may control
+     *    resources on the same server.  As a result, it may not be possible to
+     *    copy a resource to a location that appears to be on the same server.
+     *
+     *    This method is idempotent, but not safe (see Section 9.1 of
+     *    [RFC2616]).  Responses to this method MUST NOT be cached.
+     *    
+ */ + void copy(String src, String dest) throws DavException, IOException; + + /** + *
+     *    The MOVE operation on a non-collection resource is the logical
+     *    equivalent of a copy (COPY), followed by consistency maintenance
+     *    processing, followed by a delete of the source, where all three
+     *    actions are performed in a single operation.  The consistency
+     *    maintenance step allows the server to perform updates caused by the
+     *    move, such as updating all URLs, other than the Request-URI that
+     *    identifies the source resource, to point to the new destination
+     *    resource.
+     *
+     *
+     *    The Destination header MUST be present on all MOVE methods and MUST
+     *    follow all COPY requirements for the COPY part of the MOVE method.
+     *    All WebDAV-compliant resources MUST support the MOVE method.
+     *
+     *    Support for the MOVE method does not guarantee the ability to move a
+     *    resource to a particular destination.  For example, separate programs
+     *    may actually control different sets of resources on the same server.
+     *    Therefore, it may not be possible to move a resource within a
+     *    namespace that appears to belong to the same server.
+     *
+     *    If a resource exists at the destination, the destination resource
+     *    will be deleted as a side-effect of the MOVE operation, subject to
+     *    the restrictions of the Overwrite header.
+     *
+     *    This method is idempotent, but not safe (see Section 9.1 of
+     *    [RFC2616]).  Responses to this method MUST NOT be cached.
+     *    
+ */ + void move(String src, String dest) throws IOException, DavException; + + /** + *
+     *    The following sections describe the LOCK method, which is used to
+     *    take out a lock of any access type and to refresh an existing lock.
+     *    These sections on the LOCK method describe only those semantics that
+     *    are specific to the LOCK method and are independent of the access
+     *    type of the lock being requested.
+     *
+     *    Any resource that supports the LOCK method MUST, at minimum, support
+     *    the XML request and response formats defined herein.
+     *
+     *    This method is neither idempotent nor safe (see Section 9.1 of
+     *    [RFC2616]).  Responses to this method MUST NOT be cached.
+     *    
+ */ + void lock(String path); + + /** + *
+     *    The UNLOCK method removes the lock identified by the lock token in
+     *    the Lock-Token request header.  The Request-URI MUST identify a
+     *    resource within the scope of the lock.
+     *
+     *    Note that use of the Lock-Token header to provide the lock token is
+     *    not consistent with other state-changing methods, which all require
+     *    an If header with the lock token.  Thus, the If header is not needed
+     *    to provide the lock token.  Naturally, when the If header is present,
+     *    it has its normal meaning as a conditional header.
+     *
+     *    For a successful response to this method, the server MUST delete the
+     *    lock entirely.
+     *
+     *    If all resources that have been locked under the submitted lock token
+     *    cannot be unlocked, then the UNLOCK request MUST fail.
+     *
+     *    A successful response to an UNLOCK method does not mean that the
+     *    resource is necessarily unlocked.  It means that the specific lock
+     *    corresponding to the specified token no longer exists.
+     *
+     *    Any DAV-compliant resource that supports the LOCK method MUST support
+     *    the UNLOCK method.
+     *
+     *    This method is idempotent, but not safe (see Section 9.1 of
+     *    [RFC2616]).  Responses to this method MUST NOT be cached.
+     *    
+ */ + void unlock(String path); +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/AbstractPutMethod.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/AbstractPutMethod.java new file mode 100644 index 00000000..d0c92fcf --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/AbstractPutMethod.java @@ -0,0 +1,33 @@ +package org.ical4j.connector.dav.method; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpPut; + +import java.net.URI; + +class AbstractPutMethod extends HttpPut { + + public AbstractPutMethod(URI uri) { + super(uri); + setEtag(null); + } + + public AbstractPutMethod(String uri) { + super(uri); + setEtag(null); + } + + public void setEtag(String etag) { + if (etag != null) { + addHeader("If-Match", etag); + } else { + addHeader("If-None-Match", "*"); + } + } + + public boolean succeeded(HttpResponse response) { + int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/FreeBusy.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/FreeBusy.java new file mode 100644 index 00000000..ef2bc543 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/FreeBusy.java @@ -0,0 +1,35 @@ +package org.ical4j.connector.dav.method; + +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.property.Organizer; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.jackrabbit.webdav.DavConstants; + +import java.io.UnsupportedEncodingException; +import java.net.URI; + +public class FreeBusy extends HttpPost { + + public FreeBusy(URI uri, Organizer organizer) { + super(uri); + addHeader(DavConstants.HEADER_CONTENT_TYPE, "text/calendar; charset=utf-8"); + if (organizer != null) { + // Was removed from the draft, but some servers still need it + addHeader("Originator", organizer.getValue()); + } + } + + public FreeBusy(String uri, Organizer organizer) { + super(uri); + addHeader(DavConstants.HEADER_CONTENT_TYPE, "text/calendar; charset=utf-8"); + if (organizer != null) { + // Was removed from the draft, but some servers still need it + addHeader("Originator", organizer.getValue()); + } + } + + public void setQuery(Calendar query) throws UnsupportedEncodingException { + setEntity(new StringEntity(query.toString())); + } +} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/method/GetMethod.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/GetMethod.java similarity index 85% rename from src/main/java/net/fortuna/ical4j/connector/dav/method/GetMethod.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/GetMethod.java index f30890f8..b8dc8042 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/method/GetMethod.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/GetMethod.java @@ -29,13 +29,13 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.method; +package org.ical4j.connector.dav.method; -import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.Calendar; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; +import org.ical4j.connector.dav.response.GetCalendarResource; import java.io.IOException; @@ -46,7 +46,9 @@ * * @author Ben * + * @deprecated use {@link org.ical4j.connector.dav.response.GetCalendarResource} */ +@Deprecated public class GetMethod extends HttpGet { /** @@ -68,11 +70,7 @@ public GetMethod(String uri) { * @throws ParserException where calendar parsing fails */ public Calendar getCalendar(HttpResponse httpResponse) throws IOException, ParserException { - String contentType = getFirstHeader("Content-Type").getValue(); - if (contentType.startsWith("text/calendar")) { - CalendarBuilder builder = new CalendarBuilder(); - return builder.build(httpResponse.getEntity().getContent()); - } - return null; + GetCalendarResource responseHandler = new GetCalendarResource(); + return responseHandler.handleResponse(httpResponse); } } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/method/MkCalendarMethod.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/MkCalendar.java similarity index 79% rename from src/main/java/net/fortuna/ical4j/connector/dav/method/MkCalendarMethod.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/MkCalendar.java index f545c81a..a25dcdca 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/method/MkCalendarMethod.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/MkCalendar.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.method; +package org.ical4j.connector.dav.method; import org.apache.http.HttpResponse; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -45,13 +45,23 @@ * @author Ben * */ -public class MkCalendarMethod extends BaseDavRequest { +public class MkCalendar extends BaseDavRequest { + + /** + * + */ + private static final String METHOD_MKCALENDAR = "MKCALENDAR"; + + public MkCalendar(URI uri) { + super(uri); +// System.out.println("properties: " + properties.getContentSize()); + } /** * @param uri a new calendar URI */ - public MkCalendarMethod(String uri) { - super(URI.create(uri)); + public MkCalendar(String uri) { + this(URI.create(uri)); } /** @@ -59,12 +69,13 @@ public MkCalendarMethod(String uri) { */ @Override public String getMethod() { - return CalDavMethods.METHOD_MKCALENDAR; + return METHOD_MKCALENDAR; } @Override public boolean succeeded(HttpResponse response) { - return response.getStatusLine().getStatusCode() == DavServletResponse.SC_CREATED; + return response.getStatusLine().getStatusCode() == DavServletResponse.SC_CREATED + || response.getStatusLine().getStatusCode() == DavServletResponse.SC_OK; } } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/method/PrincipalPropertySearchMethod.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PrincipalPropertySearch.java similarity index 82% rename from src/main/java/net/fortuna/ical4j/connector/dav/method/PrincipalPropertySearchMethod.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PrincipalPropertySearch.java index 4154c5eb..31b89952 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/method/PrincipalPropertySearchMethod.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PrincipalPropertySearch.java @@ -29,9 +29,11 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.method; +package org.ical4j.connector.dav.method; import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest; import org.apache.jackrabbit.webdav.client.methods.XmlEntity; import org.apache.jackrabbit.webdav.header.DepthHeader; @@ -47,13 +49,13 @@ * @author probert * */ -public class PrincipalPropertySearchMethod extends BaseDavRequest { +public class PrincipalPropertySearch extends BaseDavRequest { protected boolean isDeep; - public PrincipalPropertySearchMethod(String uri, PrincipalPropertySearchInfo reportInfo) throws IOException { + public PrincipalPropertySearch(String uri, PrincipalPropertySearchInfo reportInfo) throws IOException { super(URI.create(uri)); - DepthHeader dh = new DepthHeader(reportInfo.getDepth()); + var dh = new DepthHeader(reportInfo.getDepth()); isDeep = reportInfo.getDepth() > 0; setHeader(dh.getHeaderName(), dh.getHeaderValue()); @@ -62,16 +64,16 @@ public PrincipalPropertySearchMethod(String uri, PrincipalPropertySearchInfo rep @Override public String getMethod() { - return "REPORT"; + return DavMethods.METHOD_REPORT; } @Override public boolean succeeded(HttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); if(isDeep) { - return statusCode == 207; + return statusCode == HttpStatus.SC_MULTI_STATUS; } - return statusCode == 200 || statusCode == 207; + return statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_MULTI_STATUS; } } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/method/PrincipalPropertySearchInfo.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PrincipalPropertySearchInfo.java similarity index 80% rename from src/main/java/net/fortuna/ical4j/connector/dav/method/PrincipalPropertySearchInfo.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PrincipalPropertySearchInfo.java index 3a30b0fe..92f985ab 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/method/PrincipalPropertySearchInfo.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PrincipalPropertySearchInfo.java @@ -29,35 +29,32 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.method; +package org.ical4j.connector.dav.method; -import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.xml.DomUtil; -import org.apache.jackrabbit.webdav.xml.Namespace; import org.apache.jackrabbit.webdav.xml.XmlSerializable; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.Node; public class PrincipalPropertySearchInfo implements XmlSerializable { protected Element originalElement; protected int depth; - public PrincipalPropertySearchInfo(Element reportElement, int depth) throws DavException { + public PrincipalPropertySearchInfo(Element reportElement, int depth) { this.originalElement = reportElement; this.depth = depth; } public Element toXml(Document document) { - String typeLocalName = originalElement.getLocalName(); - Namespace typeNamespace = DomUtil.getNamespace(originalElement); - Element reportElement = DomUtil.createElement(document, typeLocalName, typeNamespace); + var typeLocalName = originalElement.getLocalName(); + var typeNamespace = DomUtil.getNamespace(originalElement); + var reportElement = DomUtil.createElement(document, typeLocalName, typeNamespace); reportElement.setAttribute("type", originalElement.getAttribute("type")); reportElement.setAttribute("test", originalElement.getAttribute("test")); for (int nodeNumber = 0; nodeNumber < originalElement.getChildNodes().getLength(); nodeNumber++) { - Node node = document.importNode(originalElement.getChildNodes().item(nodeNumber), true); - reportElement.appendChild(node); + var node = document.importNode(originalElement.getChildNodes().item(nodeNumber), true); + reportElement.appendChild(node); } return reportElement; } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/method/PutMethod.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PutCalendar.java similarity index 73% rename from src/main/java/net/fortuna/ical4j/connector/dav/method/PutMethod.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PutCalendar.java index 31032af0..a544f4f0 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/method/PutMethod.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PutCalendar.java @@ -29,16 +29,14 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.method; +package org.ical4j.connector.dav.method; import net.fortuna.ical4j.data.CalendarOutputter; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.validate.ValidationException; -import net.fortuna.ical4j.vcard.VCard; -import net.fortuna.ical4j.vcard.VCardOutputter; -import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; +import org.ical4j.connector.MediaType; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -51,18 +49,16 @@ * @author Ben * */ -public class PutMethod extends HttpPut { +public class PutCalendar extends AbstractPutMethod { private final CalendarOutputter calendarOutputter; - private final VCardOutputter vCardOutputter; - + /** * @param uri a calendar URI */ - public PutMethod(String uri) { + public PutCalendar(String uri) { super(uri); - this.calendarOutputter = new CalendarOutputter(); - this.vCardOutputter = new VCardOutputter(); + this.calendarOutputter = new CalendarOutputter(false); } /** @@ -71,14 +67,9 @@ public PutMethod(String uri) { * @throws ValidationException where the specified calendar is not valid */ public void setCalendar(Calendar calendar) throws IOException, ValidationException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + var bytes = new ByteArrayOutputStream(); calendarOutputter.output(calendar, bytes); - setEntity(new ByteArrayEntity(bytes.toByteArray(), ContentType.create("text/calendar"))); - } - - public void setVCard(VCard card) throws IOException, ValidationException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - vCardOutputter.output(card, bytes); - setEntity(new ByteArrayEntity(bytes.toByteArray(), ContentType.create("text/vcard"))); + setEntity(new ByteArrayEntity(bytes.toByteArray(), + ContentType.create(MediaType.ICALENDAR_2_0.getContentType()))); } } diff --git a/src/main/java/net/fortuna/ical4j/connector/CardCollection.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PutVCard.java similarity index 64% rename from src/main/java/net/fortuna/ical4j/connector/CardCollection.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PutVCard.java index 04aed1ae..5aeb9944 100644 --- a/src/main/java/net/fortuna/ical4j/connector/CardCollection.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/PutVCard.java @@ -29,35 +29,41 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector.dav.method; -import net.fortuna.ical4j.model.ConstraintViolationException; +import net.fortuna.ical4j.validate.ValidationException; import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.VCardOutputter; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.ical4j.connector.MediaType; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; /** * $Id$ * - * Created on 27/09/2008 + * Created on 19/11/2008 * * @author Ben * */ -public interface CardCollection extends ObjectCollection { +public class PutVCard extends AbstractPutMethod { - /** - * @param card a vCard object instance - * @throws ObjectStoreException where an unexpected error occurs - * @throws ConstraintViolationException where the specified object is not valid - */ - void addCard(VCard card) throws ObjectStoreException, ConstraintViolationException; + private final VCardOutputter vCardOutputter; /** - * Remove an existing card from the collection. - * - * @param uid the uid of the existing card - * @return the card object that was removed from the collection - * @throws ObjectNotFoundException - * @throws FailedOperationException + * @param uri a calendar URI */ - VCard removeCard(String uid) throws ObjectNotFoundException, FailedOperationException; + public PutVCard(String uri) { + super(uri); + this.vCardOutputter = new VCardOutputter(); + } + + public void setVCard(VCard card) throws IOException, ValidationException { + var bytes = new ByteArrayOutputStream(); + vCardOutputter.output(card, bytes); + setEntity(new ByteArrayEntity(bytes.toByteArray(), ContentType.create(MediaType.VCARD_4_0.getContentType()))); + } } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/method/ReportMethod.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/ReportMethod.java similarity index 51% rename from src/main/java/net/fortuna/ical4j/connector/dav/method/ReportMethod.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/ReportMethod.java index c57c2c4d..8fc64b76 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/method/ReportMethod.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/method/ReportMethod.java @@ -29,31 +29,22 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.method; +package org.ical4j.connector.dav.method; -import net.fortuna.ical4j.connector.dav.CalDavConstants; -import net.fortuna.ical4j.connector.dav.property.CalDavPropertyName; -import net.fortuna.ical4j.connector.dav.property.CardDavPropertyName; -import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.vcard.VCard; -import net.fortuna.ical4j.vcard.VCardBuilder; import org.apache.http.HttpResponse; +import org.apache.http.client.ResponseHandler; import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.MultiStatusResponse; import org.apache.jackrabbit.webdav.client.methods.HttpReport; -import org.apache.jackrabbit.webdav.property.DavPropertySet; -import org.apache.jackrabbit.webdav.security.report.PrincipalMatchReport; import org.apache.jackrabbit.webdav.version.report.ReportInfo; -import org.apache.jackrabbit.webdav.version.report.ReportType; +import org.ical4j.connector.dav.CalDavSupport; +import org.ical4j.connector.dav.response.GetCalendarData; +import org.ical4j.connector.dav.response.GetVCardData; import org.w3c.dom.DOMException; import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; /** * $Id$ @@ -62,19 +53,11 @@ * * @author Ben * + * @deprecated use {@link CalDavSupport#report(String, ReportInfo, ResponseHandler)} */ +@Deprecated public class ReportMethod extends HttpReport { - /** - * - */ - public static final ReportType CALENDAR_QUERY = ReportType.register("calendar-query", CalDavConstants.CALDAV_NAMESPACE, - PrincipalMatchReport.class); - public static final ReportType FREEBUSY_QUERY = ReportType.register("free-busy-query", CalDavConstants.CALDAV_NAMESPACE, - PrincipalMatchReport.class); - public static final ReportType ADDRESSBOOK_QUERY = ReportType.register("addressbook-query", CalDavConstants.CARDDAV_NAMESPACE, - PrincipalMatchReport.class); - /** * @param uri a calendar collection URI * @param reportInfo report configuration @@ -92,35 +75,10 @@ public ReportMethod(String uri, ReportInfo reportInfo) throws IOException { * @throws ParserException where calendar parsing fails */ public Calendar[] getCalendars(HttpResponse httpResponse) throws IOException, DavException, DOMException, ParserException { - List calendars = new ArrayList(); - MultiStatus multi = getResponseBodyAsMultiStatus(httpResponse); - for (MultiStatusResponse response : multi.getResponses()) { - DavPropertySet props = response.getProperties(200); - if (props.get(CalDavPropertyName.CALENDAR_DATA) != null) { - String value = (String) props.get(CalDavPropertyName.CALENDAR_DATA).getValue(); - CalendarBuilder builder = new CalendarBuilder(); - calendars.add(builder.build(new StringReader(value))); - } - } - return calendars.toArray(new Calendar[calendars.size()]); + return new GetCalendarData().handleResponse(httpResponse).toArray(new Calendar[0]); } public VCard[] getVCards(HttpResponse httpResponse) throws IOException, DavException, DOMException { - List cards = new ArrayList(); - MultiStatus multi = getResponseBodyAsMultiStatus(httpResponse); - for (MultiStatusResponse response : multi.getResponses()) { - DavPropertySet props = response.getProperties(200); - if (props.get(CardDavPropertyName.ADDRESS_DATA) != null) { - String value = (String) props.get(CardDavPropertyName.ADDRESS_DATA).getValue(); - VCardBuilder builder = new VCardBuilder(new StringReader(value)); - try { - cards.add(builder.build()); - } catch (ParserException e) { - System.out.println(e.getMessage()); - System.out.println(value); - } - } - } - return cards.toArray(new VCard[cards.size()]); + return new GetVCardData().handleResponse(httpResponse).toArray(new VCard[0]); } } diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/property/BaseDavPropertyName.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/BaseDavPropertyName.java similarity index 50% rename from src/main/java/net/fortuna/ical4j/connector/dav/property/BaseDavPropertyName.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/BaseDavPropertyName.java index c834fe33..da52b52e 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/property/BaseDavPropertyName.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/BaseDavPropertyName.java @@ -29,13 +29,15 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.property; - -import net.fortuna.ical4j.connector.dav.CalDavConstants; -import net.fortuna.ical4j.connector.dav.DavConstants; +package org.ical4j.connector.dav.property; +import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.security.SecurityConstants; +import org.apache.jackrabbit.webdav.version.DeltaVConstants; +import org.apache.jackrabbit.webdav.version.report.ExpandPropertyReport; +import org.apache.jackrabbit.webdav.version.report.ReportType; + +import static org.apache.jackrabbit.webdav.DavConstants.NAMESPACE; /** * Collection of properties related to the DAV namespace @@ -43,60 +45,87 @@ * @author probert * */ -public class BaseDavPropertyName { +public interface BaseDavPropertyName { + ReportType EXPAND_PROPERTY = ReportType.register(DeltaVConstants.XML_EXPAND_PROPERTY, + DeltaVConstants.NAMESPACE, ExpandPropertyReport.class); /** - * - */ - public static final DavPropertyName CURRENT_USER_PRIVILEGE_SET = SecurityConstants.CURRENT_USER_PRIVILEGE_SET; + * Indicates the maximum amount of additional storage available to be allocated to a resource. RFC 4331 + */ + String PROPERTY_QUOTA_AVAILABLE_BYTES = "quota-available-bytes"; /** - * <prop> element + * Contains the amount of storage counted against the quota on a resource. RFC 4331 */ - public static final DavPropertyName PROP = DavPropertyName.create(DavConstants.XML_PROP, DavConstants.NAMESPACE); + String PROPERTY_QUOTA_USED_BYTES = "quota-used-bytes"; /** - * Contains the amount of storage counted against the quota on a resource. rfc4331 + * The DAV:resource-id property is a REQUIRED property that enables clients to determine whether two bindings + * are to the same resource. rfc5842 */ - public static final DavPropertyName QUOTA_USED_BYTES = DavPropertyName.create( - CalDavConstants.PROPERTY_QUOTA_USED_BYTES, CalDavConstants.NAMESPACE); + String PROPERTY_RESOURCE_ID = "resource-id"; /** - * - */ - public static final DavPropertyName RESOURCETYPE = DavPropertyName.create(DavConstants.PROPERTY_RESOURCETYPE, - DavConstants.NAMESPACE); + * This property identifies the reports that are supported by the resource. RFC 3253 + */ + String PROPERTY_SUPPORTED_REPORT_SET = "supported-report-set"; + + /** + * Contains the value of the synchronization token as it would be returned by a + * DAV:sync-collection report RFC 6578 + */ + String PROPERTY_SYNC_TOKEN = "sync-token"; + + /** + * DAV:add-member is a protected property (see [RFC4918], Section 15) defined on WebDAV collections, + * and contains the "Add-Member" URI for that collection. RFC 5995 + */ + String PROPERTY_ADD_MEMBER = "add-member"; + + /** + * <prop> element + */ + DavPropertyName PROP = DavPropertyName.create(DavConstants.XML_PROP, NAMESPACE); + + /** + * Contains the amount of storage counted against the quota on a resource. rfc4331 + */ + DavPropertyName QUOTA_USED_BYTES = DavPropertyName.create( + PROPERTY_QUOTA_USED_BYTES, NAMESPACE); /** * Indicates the maximum amount of additional storage available to be allocated to a resource rfc4331 */ - public static final DavPropertyName QUOTA_AVAILABLE_BYTES = DavPropertyName.create( - CalDavConstants.PROPERTY_QUOTA_AVAILABLE_BYTES, CalDavConstants.NAMESPACE); + DavPropertyName QUOTA_AVAILABLE_BYTES = DavPropertyName.create( + PROPERTY_QUOTA_AVAILABLE_BYTES, NAMESPACE); /** * The DAV:resource-id property is a REQUIRED property that enables clients to determine whether two bindings * are to the same resource. rfc5842 */ - public static final DavPropertyName RESOURCE_ID = DavPropertyName.create( - DavConstants.PROPERTY_RESOURCE_ID, DavConstants.NAMESPACE); + DavPropertyName RESOURCE_ID = DavPropertyName.create( + PROPERTY_RESOURCE_ID, NAMESPACE); /** * This property identifies the reports that are supported by the resource. RFC 3253 */ - public static final DavPropertyName SUPPORTED_REPORT_SET = DavPropertyName.create( - DavConstants.PROPERTY_SUPPORTED_REPORT_SET, DavConstants.NAMESPACE); + DavPropertyName SUPPORTED_REPORT_SET = DavPropertyName.create( + PROPERTY_SUPPORTED_REPORT_SET, NAMESPACE); /** * Contains the value of the synchronization token as it would be returned by a * DAV:sync-collection report RFC 6578 */ - public static final DavPropertyName SYNC_TOKEN = DavPropertyName.create( - DavConstants.PROPERTY_SYNC_TOKEN, DavConstants.NAMESPACE); + DavPropertyName SYNC_TOKEN = DavPropertyName.create( + PROPERTY_SYNC_TOKEN, NAMESPACE); /** * DAV:add-member is a protected property (see [RFC4918], Section 15) defined on WebDAV collections, * and contains the "Add-Member" URI for that collection. RFC 5995 */ - public static final DavPropertyName ADD_MEMBER = DavPropertyName.create( - DavConstants.PROPERTY_ADD_MEMBER, DavConstants.NAMESPACE); + DavPropertyName ADD_MEMBER = DavPropertyName.create( + PROPERTY_ADD_MEMBER, NAMESPACE); + + DavPropertyName CURRENT_USER_PRINCIPAL = DavPropertyName.create( + "current-user-principal", NAMESPACE); } diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CSDavPropertyName.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CSDavPropertyName.java new file mode 100644 index 00000000..85c56c10 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CSDavPropertyName.java @@ -0,0 +1,201 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav.property; + +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.xml.Namespace; + +/** + * Properties that belongs to the CalendarServer namespace. + * + * @author probert + * + */ +public interface CSDavPropertyName { + + /** + * To improve on performance, this specification defines a new "calendar collection entity tag" (CTag) WebDAV + * property that is defined on calendar collections. When the calendar collection changes, the CTag value changes. + * Source : https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt + */ + String PROPERTY_CTAG = "getctag"; + + /** + * Specific to CalendarServer, but I can't find the description + */ + String PROPERTY_DROP_HOME_URL = "dropbox-home-URL"; + + /** + * Provides the URI of the pubsub node to subscribe to in order to receive a notification whenever a + * resource within this calendar home has changed. + * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-pubsubdiscovery.txt + */ + String PROPERTY_XMPP_URI = "xmpp-uri"; + + /** + * Identify the URL of the notification collection owned by the associated principal resource. + * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt + */ + String PROPERTY_NOTIFICATION_URL = "notification-URL"; + + /** + * Provides the hostname of the XMPP server a client should connect to for subscribing to notifications. + * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-pubsubdiscovery.txt + */ + String PROPERTY_XMPP_SERVER = "xmpp-server"; + + /** + * Auto-accept is currently supported for location and resource records. Specific to Calendar/iCal Server + */ + String PROPERTY_AUTO_SCHEDULE = "auto-schedule"; + + /** + */ + String PROPERTY_SOURCE = "source"; + + /** + */ + String PROPERTY_SUBSCRIBED_STRIP_ALARMS = "subscribed-strip-alarms"; + + /** + */ + String PROPERTY_SUBSCRIBED_STRIP_ATTACHMENTS = "subscribed-strip-attachments"; + + /** + */ + String PROPERTY_SUBSCRIBED_STRIP_TODOS = "subscribed-strip-todos"; + + /** + */ + String PROPERTY_REFRESHRATE = "refreshrate"; + + /** + */ + String PROPERTY_PUSH_TRANSPORTS = "push-transports"; + + /** + */ + String PROPERTY_PUSHKEY = "pushkey"; + + /** + * Namespace used by CalendarServer (calendarserver.org). + */ + Namespace NAMESPACE = Namespace.getNamespace("S", "http://calendarserver.org/ns/"); + + /** + * To improve on performance, this specification defines a new "calendar collection entity tag" (CTag) WebDAV + * property that is defined on calendar collections. When the calendar collection changes, the CTag value changes. + * Source : https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt + */ + DavPropertyName CTAG = DavPropertyName.create(PROPERTY_CTAG, NAMESPACE); + + /** + * TODO: description missing. Stuff coming from Apple's Calendar Server + */ + String PROPERTY_PROXY_WRITE_FOR = "calendar-proxy-write-for"; + /** + * TODO: description missing. Stuff coming from Apple's Calendar Server + */ + DavPropertyName PROXY_WRITE_FOR = DavPropertyName.create(PROPERTY_PROXY_WRITE_FOR, NAMESPACE); + + /** + * TODO: description missing. Stuff coming from Apple's Calendar Server + */ + String PROPERTY_PROXY_READ_FOR = "calendar-proxy-read-for"; + /** + * TODO: description missing. Stuff coming from Apple's Calendar Server + */ + DavPropertyName PROXY_READ_FOR = DavPropertyName.create(PROPERTY_PROXY_READ_FOR, NAMESPACE); + + /** + * Specific to CalendarServer, but I can't find the description + */ + DavPropertyName DROP_HOME_URL = DavPropertyName.create(PROPERTY_DROP_HOME_URL, NAMESPACE); + + /** + * Provides the URI of the pubsub node to subscribe to in order to receive a notification whenever a resource within + * this calendar home has changed. + * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions + * /caldav-pubsubdiscovery.txt + */ + DavPropertyName XMPP_URI = DavPropertyName.create(PROPERTY_XMPP_URI, NAMESPACE); + + /** + * Identify the URL of the notification collection owned by the associated principal resource. + * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt + */ + DavPropertyName NOTIFICATION_URL = DavPropertyName.create(PROPERTY_NOTIFICATION_URL, NAMESPACE); + + /** + * Provides the hostname of the XMPP server a client should connect to for subscribing to notifications. + * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-pubsubdiscovery.txt + */ + DavPropertyName XMPP_SERVER = DavPropertyName.create(PROPERTY_XMPP_SERVER, NAMESPACE); + + /** + * Auto-accept is currently supported for location and resource records. Specific to Calendar/iCal Server + */ + DavPropertyName AUTO_SCHEDULE = DavPropertyName.create(PROPERTY_AUTO_SCHEDULE, NAMESPACE); + + /** + */ + DavPropertyName SOURCE = DavPropertyName.create(DavConstants.PROPERTY_SOURCE, NAMESPACE); + + /** + */ + DavPropertyName SUBSCRIBED_STRIP_ALARMS = DavPropertyName.create( + PROPERTY_SUBSCRIBED_STRIP_ALARMS, NAMESPACE); + + /** + */ + DavPropertyName SUBSCRIBED_STRIP_ATTACHMENTS = DavPropertyName.create( + PROPERTY_SUBSCRIBED_STRIP_ATTACHMENTS, NAMESPACE); + + /** + */ + DavPropertyName SUBSCRIBED_STRIP_TODOS = DavPropertyName.create(PROPERTY_SUBSCRIBED_STRIP_TODOS, + NAMESPACE); + + /** + */ + DavPropertyName REFRESHRATE = DavPropertyName.create(PROPERTY_REFRESHRATE, NAMESPACE); + + /** + */ + DavPropertyName PUSH_TRANSPORTS = DavPropertyName.create(PROPERTY_PUSH_TRANSPORTS, NAMESPACE); + + /** + */ + DavPropertyName PUSHKEY = DavPropertyName.create(PROPERTY_PUSHKEY, NAMESPACE); + +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CalDavPropertyName.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CalDavPropertyName.java new file mode 100644 index 00000000..d7610ab0 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CalDavPropertyName.java @@ -0,0 +1,495 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav.property; + +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.security.report.PrincipalMatchReport; +import org.apache.jackrabbit.webdav.version.report.ReportType; +import org.apache.jackrabbit.webdav.xml.Namespace; + +/** + * $Id$ + * + * Created on 19/11/2008 + * + * @author Ben + * + */ +public interface CalDavPropertyName { + + /** + * + */ + String PROPERTY_FILTER = "filter"; + /** + * Purpose: Provides a human-readable description of the calendar collection. RFC : rfc4791 + */ + String PROPERTY_CALENDAR_DESCRIPTION = "calendar-description"; + + /** + * Purpose: Identify the calendars that contribute to the free-busy information for the owner of the scheduling + * https://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04 + * + * THIS PROPERTY WAS REMOVED IN DRAFT 05 AND THE OFFICIAL RFC (6638) + */ + String PROPERTY_FREE_BUSY_SET = "calendar-free-busy-set"; + + /** + * Purpose: Identifies the URL of any WebDAV collections that contain calendar collections owned by the associated + * principal resource. RFC : rfc4791 + */ + String PROPERTY_CALENDAR_HOME_SET = "calendar-home-set"; + + /** + * The property to identify a "calendar" resource-type for the collection. + */ + String PROPERTY_RESOURCETYPE_CALENDAR = "calendar"; + + /** + * Identify the URL of the scheduling Inbox collection owned by the associated principal resource. + * https://tools.ietf.org/html/rfc6638 + */ + String PROPERTY_SCHEDULE_INBOX_URL = "schedule-inbox-URL"; + + /** + * Identify the URL of the scheduling Outbox collection owned by the associated principal resource. + * https://tools.ietf.org/html/rfc6638 + */ + String PROPERTY_SCHEDULE_OUTBOX_URL = "schedule-outbox-URL"; + + /** + * Specifies the calendar component types (e.g., VEVENT, VTODO, etc.) that calendar object resources can contain in + * the calendar collection. + */ + String PROPERTY_SUPPORTED_CALENDAR_COMPONENT_SET = "supported-calendar-component-set"; + + /** + * Specifies a supported component type (e.g., VEVENT, VTODO, etc.) + */ + String PROPERTY_COMPONENT = "comp"; + + /** + * The CALDAV:calendar-timezone property is used to specify the time zone the server should rely on to resolve + * "date" values and "date with local time" values (i.e., floating time) to "date with UTC time" values. + */ + String PROPERTY_CALENDAR_TIMEZONE = "calendar-timezone"; + + /** + * + */ + String PROPERTY_SUPPORTED_CALENDAR_DATA = "supported-calendar-data"; + + /** + * + */ + String PROPERTY_MAX_RESOURCE_SIZE = "max-resource-size"; + + /** + * + */ + String PROPERTY_MIN_DATE_TIME = "min-date-time"; + + /** + * + */ + String PROPERTY_MAX_DATE_TIME = "max-date-time"; + + /** + * + */ + String PROPERTY_MAX_INSTANCES = "max-instances"; + + /** + * + */ + String PROPERTY_MAX_ATTENDEES_PER_INSTANCE = "max-attendees-per-instance"; + + /** + * + */ + String PROPERTY_CALENDAR_QUERY = "calendar-query"; + + /** + * Servers MAY reject requests to create a + * scheduling object resource with an iCalendar "UID" property value + * already in use by another scheduling object resource owned by the + * same user in other calendar collections. Servers SHOULD report + * the URL of the scheduling object resource that is already making + * use of the same "UID" property value in the DAV:href element. + * + * https://tools.ietf.org/html/rfc6638 + */ + String UNIQUE_SCHEDULING_OBJECT_RESOURCE = "unique-scheduling-object-resource"; + + /** + * All the calendar components in a + * scheduling object resource MUST contain the same "ORGANIZER" + * property value when present + * + * https://tools.ietf.org/html/rfc6638 + */ + String SAME_ORGANIZER_IN_ALL_COMPONENTS = "same-organizer-in-all-components"; + + /** + * Servers MAY impose restrictions on modifications allowed by an "Organizer". + * + * https://tools.ietf.org/html/rfc6638 + */ + String ALLOWED_ORGANIZER_SCHEDULING_OBJECT_CHANGE = "allowed-organizer-scheduling-object-change"; + + /** + * Servers MAY impose restrictions on modifications allowed by an "Attendee", + * subject to the allowed changes specified in Section 3.2.2.1 + * + * https://tools.ietf.org/html/rfc6638 + */ + String ALLOWED_ATTENDEE_SCHEDULING_OBJECT_CHANGE = "allowed-attendee-scheduling-object-change"; + + /** + * https://tools.ietf.org/html/rfc6638 + */ + String DEFAULT_CALENDAR_NEEDED = "default-calendar-needed"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String VALID_SCHEDULE_DEFAULT_CALENDAR_URL = "valid-schedule-default-calendar-URL"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String VALID_SCHEDULING_MESSAGE = "valid-scheduling-message"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String VALID_ORGANIZER = "valid-organizer"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String SCHEDULE_DELIVER = "schedule-deliver"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String SCHEDULE_DELIVER_INVITE = "schedule-deliver-invite"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String SCHEDULE_DELIVER_REPLY = "schedule-deliver-reply"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String SCHEDULE_QUERY_FREEBUSY = "schedule-query-freebusy"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String SCHEDULE_SEND = "schedule-send"; + /** + * https://tools.ietf.org/html/rfc6638 + */ + String SCHEDULE_SEND_INVITE = "schedule-send-invite"; + + /** + * The CALDAV:schedule-send-reply privilege controls the sending of + * scheduling messages by "Attendees". + * + * https://tools.ietf.org/html/rfc6638#section-6.2.3 + */ + String SCHEDULE_SEND_REPLY = "schedule-send-reply"; + + /** + * The CALDAV:schedule-send-freebusy privilege controls the use of the + * POST method to submit scheduling messages that specify the scheduling + * method "REQUEST" with a "VFREEBUSY" calendar component. + * + * https://tools.ietf.org/html/rfc6638#section-6.2.4 + */ + String SCHEDULE_SEND_FREEBUSY = "schedule-send-freebusy"; + + /** + * Determines whether the calendar object resources in a + * calendar collection will affect the owner's busy time information. + * + * https://tools.ietf.org/html/rfc6638#section-9.1 + */ + String PROPERTY_SCHEDULE_CALENDAR_TRANSP = "schedule-calendar-transp"; + + /** + * Specifies a default calendar for an "Attendee" where new + * scheduling object resources are created. + * + * https://tools.ietf.org/html/rfc6638#section-9.2 + */ + String PROPERTY_SCHEDULE_DEFAULT_CALENDAR_URL = "schedule-default-calendar-URL"; + + /** + * Indicates whether a scheduling object resource has had a + * "consequential" change made to it. + * + * https://tools.ietf.org/html/rfc6638#section-9.3 + */ + String SCHEDULE_TAG = "schedule-tag"; + + /** + * Contains the set of responses for a POST method request (for scheduling) + * + * https://tools.ietf.org/html/rfc6638#section-10.1 + */ + String SCHEDULE_RESPONSE = "schedule-response"; + + /** + * Contains a single response for a POST method request (for scheduling) + * + * https://tools.ietf.org/html/rfc6638#section-10.2 + */ + String RESPONSE = "response"; + + /** + * The calendar user address that the enclosing response for a POST method request is for. + * + * https://tools.ietf.org/html/rfc6638#section-10.3 + */ + String PROPERTY_RECIPIENT = "recipient"; + + /** + * The iTIP "REQUEST-STATUS" property value for a scheduling response. + * + * https://tools.ietf.org/html/rfc6638#section-10.4 + */ + String PROPERTY_REQUEST_STATUS = "request-status"; + + /** + * Defines a "VAVAILABILITY" component that will be used in calculating + * free-busy time when an iTIP free-busy request is targeted at the + * calendar user who owns the Inbox. + * + * http://tools.ietf.org/html/draft-daboo-calendar-availability-03 + */ + String CALENDAR_AVAIBILITY = "calendar-availability"; + + /** + * Enumerates the sets of component restrictions the server is + * willing to allow the client to specify in MKCALENDAR or extended + * MKCOL requests. + * + * http://tools.ietf.org/html/draft-daboo-caldav-extensions-01 + */ + String SUPPORTED_CALENDAR_COMPONENT_SETS = "supported-calendar-component-sets"; + + /** + * A default alarm applied to "VEVENT" components whose "DTSTART" property value + * type is "DATE-TIME" + * + * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 + */ + String DEFAULT_ALARM_VEVENT_DATETIME = "default-alarm-vevent-datetime"; + + /** + * A default alarm applied to "VEVENT" components whose "DTSTART" property value type is "DATE" + * + * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 + */ + String DEFAULT_ALARM_VEVENT_DATE = "default-alarm-vevent-date"; + + /** + * A default alarm applied to "VTODO" components whose "DUE" or "DTSTART" + * property value type is "DATE-TIME" + * + * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 + */ + String DEFAULT_ALARM_VTODO_DATETIME = "default-alarm-vtodo-datetime"; + + /** + * A default alarm applied to "VTODO" components whose "DUE" or "DTSTART" + * property value type is "DATE", or when neither of those properties is present + * + * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 + */ + String DEFAULT_ALARM_VTODO_DATE = "default-alarm-vtodo-date"; + + /** + * Default namespace. + */ + Namespace NAMESPACE = Namespace.getNamespace("C", "urn:ietf:params:xml:ns:caldav"); + ReportType FREEBUSY_QUERY = ReportType.register("free-busy-query", NAMESPACE, + PrincipalMatchReport.class); + /** + * + */ + ReportType CALENDAR_QUERY = ReportType.register("calendar-query", NAMESPACE, + PrincipalMatchReport.class); + + /** + * + */ + DavPropertyName CALENDAR_DESCRIPTION = DavPropertyName.create( + PROPERTY_CALENDAR_DESCRIPTION, NAMESPACE); + + /** + * + */ + DavPropertyName CALENDAR_TIMEZONE = DavPropertyName.create( + PROPERTY_CALENDAR_TIMEZONE, NAMESPACE); + + /** + * + */ + DavPropertyName SUPPORTED_CALENDAR_COMPONENT_SET = DavPropertyName.create( + PROPERTY_SUPPORTED_CALENDAR_COMPONENT_SET, NAMESPACE); + + /** + * + */ + DavPropertyName SUPPORTED_CALENDAR_DATA = DavPropertyName.create( + PROPERTY_SUPPORTED_CALENDAR_DATA, NAMESPACE); + + /** + * + */ + DavPropertyName MAX_RESOURCE_SIZE = DavPropertyName.create( + PROPERTY_MAX_RESOURCE_SIZE, NAMESPACE); + + /** + * + */ + DavPropertyName MIN_DATE_TIME = DavPropertyName.create(PROPERTY_MIN_DATE_TIME, + NAMESPACE); + + /** + * + */ + DavPropertyName MAX_DATE_TIME = DavPropertyName.create(PROPERTY_MAX_DATE_TIME, + NAMESPACE); + + /** + * + */ + DavPropertyName MAX_INSTANCES = DavPropertyName.create(PROPERTY_MAX_INSTANCES, + NAMESPACE); + + /** + * + */ + DavPropertyName MAX_ATTENDEES_PER_INSTANCE = DavPropertyName.create( + PROPERTY_MAX_ATTENDEES_PER_INSTANCE, NAMESPACE); + + /** + * + */ + String PROPERTY_CALENDAR_DATA = "calendar-data"; + /** + * + */ + DavPropertyName CALENDAR_DATA = DavPropertyName.create(PROPERTY_CALENDAR_DATA, + NAMESPACE); + + /** + * Property from a draft (draft-desruisseaux-ischedule-01) + */ + DavPropertyName RECIPIENT = DavPropertyName.create(PROPERTY_RECIPIENT, + NAMESPACE); + + /** + * Property from a draft (draft-desruisseaux-ischedule-01) + */ + DavPropertyName REQUEST_STATUS = DavPropertyName.create( + PROPERTY_REQUEST_STATUS, NAMESPACE); + + DavPropertyName COMPONENT = DavPropertyName.create(PROPERTY_COMPONENT, + NAMESPACE); + + /** + * Purpose: Identify the calendars that contribute to the free-busy information for the owner of the scheduling + * https://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04 + * + * THIS PROPERTY WAS REMOVED IN DRAFT 05 AND THE OFFICIAL RFC (6638) + * + */ + DavPropertyName FREE_BUSY_SET = DavPropertyName.create(PROPERTY_FREE_BUSY_SET, + NAMESPACE); + + /** + * Purpose: Identifies the URL of any WebDAV collections that contain calendar collections owned by the associated + * principal resource. RFC : rfc4791 + */ + DavPropertyName CALENDAR_HOME_SET = DavPropertyName.create(PROPERTY_CALENDAR_HOME_SET, + NAMESPACE); + + /** + * Identify the calendar addresses of the associated principal resource. + * https://tools.ietf.org/html/rfc6638 + */ + String PROPERTY_USER_ADDRESS_SET = "calendar-user-address-set"; + /** + * Identify the calendar addresses of the associated principal resource. + * http://tools.ietf.org/html/rfc6638 + */ + DavPropertyName USER_ADDRESS_SET = DavPropertyName.create( + PROPERTY_USER_ADDRESS_SET, NAMESPACE); + + /** + * Identifies the calendar user type of the associated principal resource. Its value is the same as the iCalendar "CUTYPE". + * https://tools.ietf.org/html/rfc6638 + */ + String PROPERTY_USER_TYPE= "calendar-user-type"; + /** + * Identifies the calendar user type of the associated principal resource. Its value is the same as the iCalendar "CUTYPE". + * https://tools.ietf.org/html/rfc6638 + */ + DavPropertyName USER_TYPE = DavPropertyName.create( + PROPERTY_USER_TYPE, NAMESPACE); + + /** + * Identify the URL of the scheduling Inbox collection owned by the associated principal resource. + * http://tools.ietf.org/html/rfc6638 + */ + DavPropertyName SCHEDULE_INBOX_URL = DavPropertyName.create( + PROPERTY_SCHEDULE_INBOX_URL, NAMESPACE); + + /** + * Identify the URL of the scheduling Outbox collection owned by the associated principal resource. + * http://tools.ietf.org/html/rfc6638 + */ + DavPropertyName SCHEDULE_OUTBOX_URL = DavPropertyName.create( + PROPERTY_SCHEDULE_OUTBOX_URL, NAMESPACE); + + /** + * Determines whether the calendar object resources in a calendar collection will affect the owner's freebusy. + */ + DavPropertyName SCHEDULE_CALENDAR_TRANSP = DavPropertyName.create( + PROPERTY_SCHEDULE_CALENDAR_TRANSP, NAMESPACE); + + /** + * Specifies a default calendar for an attendee that will automatically have new scheduling messages deposited into + * it when they arrive. + */ + DavPropertyName SCHEDULE_DEFAULT_CALENDAR_URL = DavPropertyName.create( + PROPERTY_SCHEDULE_DEFAULT_CALENDAR_URL, NAMESPACE); + +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CardDavPropertyName.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CardDavPropertyName.java new file mode 100644 index 00000000..a1b78f24 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/CardDavPropertyName.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav.property; + +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.security.report.PrincipalMatchReport; +import org.apache.jackrabbit.webdav.version.report.ReportType; +import org.apache.jackrabbit.webdav.xml.Namespace; + +/** + * $Id$ + * + * Created on 19/11/2008 + * + * @author Ben + * + */ +public interface CardDavPropertyName { + + /** + * Identifies the URL of any WebDAV collections that contain address book collections + * owned by the associated principal resource. rfc6352 + */ + String PROPERTY_ADDRESSBOOK_HOME_SET = "addressbook-home-set"; + + /** + * Specifies what media types are allowed for address object resources in an address + * book collection. rfc6352 + */ + String PROPERTY_SUPPORTED_ADDRESS_DATA = "supported-address-data"; + + /** + * for carddav + */ + String PROPERTY_MAX_IMAGE_SIZE = "max-image-size"; + + /** + * Specifies one of the following: + * + * 1. The parts of an address object resource that should be + * returned by a given address book REPORT request, and the media + * type and version for the returned data; or + * + * 2. The content of an address object resource in a response to an + * address book REPORT request. + * + * RFC 6352 + */ + String PROPERTY_ADDRESS_DATA = "address-data"; + + /** + * + */ + String PROPERTY_MAX_RESOURCE_SIZE = "max-resource-size"; + + /** + * CardDAV namespace + */ + Namespace NAMESPACE = Namespace.getNamespace("C", "urn:ietf:params:xml:ns:carddav"); + + ReportType ADDRESSBOOK_QUERY = ReportType.register("addressbook-query", NAMESPACE, + PrincipalMatchReport.class); + /** + * + */ + DavPropertyName MAX_RESOURCE_SIZE = DavPropertyName.create( + PROPERTY_MAX_RESOURCE_SIZE, NAMESPACE); + + /** + * Purpose: Identifies the URL of any WebDAV collections that contain address book collections owned by the associated + * principal resource. RFC : rfc6352 + */ + DavPropertyName ADDRESSBOOK_HOME_SET = DavPropertyName.create(PROPERTY_ADDRESSBOOK_HOME_SET, + NAMESPACE); + + /** + * + */ + DavPropertyName SUPPORTED_ADDRESS_DATA = DavPropertyName.create( + PROPERTY_SUPPORTED_ADDRESS_DATA, NAMESPACE); + + /** + * + */ + DavPropertyName MAX_IMAGE_SIZE = DavPropertyName.create( + PROPERTY_MAX_IMAGE_SIZE, NAMESPACE); + + /** + * + */ + DavPropertyName ADDRESS_DATA = DavPropertyName.create( + PROPERTY_ADDRESS_DATA, NAMESPACE); + + DavPropertyName ADDRESSBOOK_DESCRIPTION = DavPropertyName.create("addressbook-description", NAMESPACE); +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/DavPropertyBuilder.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/DavPropertyBuilder.java new file mode 100644 index 00000000..5bcd7de8 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/DavPropertyBuilder.java @@ -0,0 +1,37 @@ +package org.ical4j.connector.dav.property; + +import org.apache.jackrabbit.webdav.property.DavProperty; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DefaultDavProperty; +import org.apache.jackrabbit.webdav.property.HrefProperty; +import org.apache.jackrabbit.webdav.security.SecurityConstants; + +import java.util.Arrays; +import java.util.List; + +public class DavPropertyBuilder { + + private static final List hrefProps = Arrays.asList(SecurityConstants.PRINCIPAL_COLLECTION_SET); + + private DavPropertyName name; + + private T value; + + public DavPropertyBuilder name(DavPropertyName name) { + this.name = name; + return this; + } + + public DavPropertyBuilder value(T value) { + this.value = value; + return this; + } + + @SuppressWarnings("unchecked") + public DavProperty build() { + if (hrefProps.contains(name)) { + return (DavProperty) new HrefProperty(name, (String[]) value, false); + } + return new DefaultDavProperty<>(name, value); + } +} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/property/ICalPropertyName.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/ICalPropertyName.java similarity index 70% rename from src/main/java/net/fortuna/ical4j/connector/dav/property/ICalPropertyName.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/ICalPropertyName.java index 706eb563..f563a051 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/property/ICalPropertyName.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/ICalPropertyName.java @@ -29,11 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.property; - -import net.fortuna.ical4j.connector.dav.CalDavConstants; +package org.ical4j.connector.dav.property; import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.xml.Namespace; /** * This class regroups all properties that are specific to the iCal (OS X client) namespace. @@ -41,12 +40,25 @@ * @author probert * */ -public class ICalPropertyName { +public interface ICalPropertyName { + + /** + * Apple's iCal client use this property to store the color of the calendar, set by the user in iCal. + */ + String PROPERTY_CALENDAR_COLOR = "calendar-color"; + + /** + * + */ + String PROPERTY_CALENDAR_ORDER = "calendar-order"; + + /** + * Namespace used by the iCal client from Apple. + */ + Namespace ICAL_NAMESPACE = Namespace.getNamespace("I", "http://apple.com/ns/ical/"); - public static final DavPropertyName CALENDAR_COLOR = DavPropertyName.create( - CalDavConstants.PROPERTY_CALENDAR_COLOR, CalDavConstants.ICAL_NAMESPACE); + DavPropertyName CALENDAR_COLOR = DavPropertyName.create(PROPERTY_CALENDAR_COLOR, ICAL_NAMESPACE); - public static final DavPropertyName CALENDAR_ORDER = DavPropertyName.create( - CalDavConstants.PROPERTY_CALENDAR_ORDER, CalDavConstants.ICAL_NAMESPACE); + DavPropertyName CALENDAR_ORDER = DavPropertyName.create(PROPERTY_CALENDAR_ORDER, ICAL_NAMESPACE); } diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/PropertyNameSets.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/PropertyNameSets.java new file mode 100644 index 00000000..21b41011 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/property/PropertyNameSets.java @@ -0,0 +1,115 @@ +package org.ical4j.connector.dav.property; + +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; +import org.apache.jackrabbit.webdav.security.SecurityConstants; + +import static org.apache.jackrabbit.webdav.property.DavPropertyName.DISPLAYNAME; + +public abstract class PropertyNameSets { + + public static final DavPropertyNameSet REPORT_CALENDAR = new DavPropertyNameSet(); + + public static final DavPropertyNameSet PROPFIND_CALENDAR = new DavPropertyNameSet(); + + public static final DavPropertyNameSet PROPFIND_CALENDAR_HOME = new DavPropertyNameSet(); + + public static final DavPropertyNameSet PROPFIND_CARD = new DavPropertyNameSet(); + + public static final DavPropertyNameSet PROPFIND_CARD_HOME = new DavPropertyNameSet(); + + public static final DavPropertyNameSet REPORT_CARD = new DavPropertyNameSet(); + + public static final DavPropertyNameSet PROPFIND_SUPPORTED_FEATURES = new DavPropertyNameSet(); + + static { + REPORT_CALENDAR.add(DavPropertyName.GETETAG); + REPORT_CALENDAR.add(CalDavPropertyName.CALENDAR_DATA); + + PROPFIND_CALENDAR.add(BaseDavPropertyName.QUOTA_AVAILABLE_BYTES); + PROPFIND_CALENDAR.add(BaseDavPropertyName.QUOTA_USED_BYTES); + PROPFIND_CALENDAR.add(SecurityConstants.CURRENT_USER_PRIVILEGE_SET); + PROPFIND_CALENDAR.add(BaseDavPropertyName.PROP); + PROPFIND_CALENDAR.add(DavPropertyName.RESOURCETYPE); + PROPFIND_CALENDAR.add(DISPLAYNAME); + PROPFIND_CALENDAR.add(SecurityConstants.OWNER); + + PROPFIND_CALENDAR.add(CalDavPropertyName.CALENDAR_DESCRIPTION); + PROPFIND_CALENDAR.add(CalDavPropertyName.SUPPORTED_CALENDAR_COMPONENT_SET); + PROPFIND_CALENDAR.add(CalDavPropertyName.FREE_BUSY_SET); + PROPFIND_CALENDAR.add(CalDavPropertyName.SCHEDULE_CALENDAR_TRANSP); + PROPFIND_CALENDAR.add(CalDavPropertyName.SCHEDULE_DEFAULT_CALENDAR_URL); + PROPFIND_CALENDAR.add(CalDavPropertyName.CALENDAR_TIMEZONE); + PROPFIND_CALENDAR.add(CalDavPropertyName.SUPPORTED_CALENDAR_DATA); + PROPFIND_CALENDAR.add(CalDavPropertyName.MAX_ATTENDEES_PER_INSTANCE); + PROPFIND_CALENDAR.add(CalDavPropertyName.MAX_DATE_TIME); + PROPFIND_CALENDAR.add(CalDavPropertyName.MIN_DATE_TIME); + PROPFIND_CALENDAR.add(CalDavPropertyName.MAX_INSTANCES); + PROPFIND_CALENDAR.add(CalDavPropertyName.MAX_RESOURCE_SIZE); + + PROPFIND_CALENDAR.add(CSDavPropertyName.XMPP_SERVER); + PROPFIND_CALENDAR.add(CSDavPropertyName.XMPP_URI); + PROPFIND_CALENDAR.add(CSDavPropertyName.CTAG); + PROPFIND_CALENDAR.add(CSDavPropertyName.SOURCE); + PROPFIND_CALENDAR.add(CSDavPropertyName.SUBSCRIBED_STRIP_ALARMS); + PROPFIND_CALENDAR.add(CSDavPropertyName.SUBSCRIBED_STRIP_ATTACHMENTS); + PROPFIND_CALENDAR.add(CSDavPropertyName.SUBSCRIBED_STRIP_TODOS); + PROPFIND_CALENDAR.add(CSDavPropertyName.REFRESHRATE); + PROPFIND_CALENDAR.add(CSDavPropertyName.PUSH_TRANSPORTS); + PROPFIND_CALENDAR.add(CSDavPropertyName.PUSHKEY); + + PROPFIND_CALENDAR.add(ICalPropertyName.CALENDAR_COLOR); + PROPFIND_CALENDAR.add(ICalPropertyName.CALENDAR_ORDER); + + PROPFIND_CALENDAR_HOME.add(CalDavPropertyName.CALENDAR_HOME_SET); + PROPFIND_CALENDAR_HOME.add(DavPropertyName.DISPLAYNAME); + + + /* + * TODO : to add the following properties + + + + + */ + + PROPFIND_CARD.add(DISPLAYNAME); + + + PROPFIND_CARD.add(SecurityConstants.CURRENT_USER_PRIVILEGE_SET); + PROPFIND_CARD.add(DavPropertyName.RESOURCETYPE); + PROPFIND_CARD.add(SecurityConstants.OWNER); + PROPFIND_CARD.add(CardDavPropertyName.MAX_RESOURCE_SIZE); + PROPFIND_CARD.add(BaseDavPropertyName.RESOURCE_ID); + PROPFIND_CARD.add(BaseDavPropertyName.SUPPORTED_REPORT_SET); + PROPFIND_CARD.add(BaseDavPropertyName.SYNC_TOKEN); + PROPFIND_CARD.add(BaseDavPropertyName.ADD_MEMBER); + PROPFIND_CARD.add(CardDavPropertyName.MAX_IMAGE_SIZE); + + /** + * FIXME jackrabbit generates an error when quota-used-bytes is sent. + * I suspect the problem is that the response have this attribute: e:dt="int" + */ + //PROPFIND_CARD.add(BaseDavPropertyName.QUOTA_USED_BYTES); + //PROPFIND_CARD.add(BaseDavPropertyName.QUOTA_AVAILABLE_BYTES); + + /* In the absence of this property, the server MUST only accept data with the media type + * "text/vcard" and vCard version 3.0, and clients can assume that is + * all the server will accept. + */ + PROPFIND_CARD.add(CardDavPropertyName.SUPPORTED_ADDRESS_DATA); + + REPORT_CARD.add(DavPropertyName.GETETAG); + REPORT_CARD.add(CardDavPropertyName.ADDRESS_DATA); + + PROPFIND_CARD_HOME.add(CardDavPropertyName.ADDRESSBOOK_HOME_SET); + PROPFIND_CARD_HOME.add(DavPropertyName.DISPLAYNAME); + + PROPFIND_SUPPORTED_FEATURES.add(DavPropertyName.RESOURCETYPE); + PROPFIND_SUPPORTED_FEATURES.add(CSDavPropertyName.CTAG); + var owner = DavPropertyName.create(DavPropertyName.XML_OWNER, DavConstants.NAMESPACE); + PROPFIND_SUPPORTED_FEATURES.add(owner); + + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarHomeSet.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarHomeSet.java new file mode 100644 index 00000000..4f7affaf --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarHomeSet.java @@ -0,0 +1,4 @@ +package org.ical4j.connector.dav.request; + +public class CalendarHomeSet { +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarMultiget.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarMultiget.java new file mode 100644 index 00000000..9092b0f1 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarMultiget.java @@ -0,0 +1,4 @@ +package org.ical4j.connector.dav.request; + +public class CalendarMultiget { +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarQuery.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarQuery.java new file mode 100644 index 00000000..97e76325 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/CalendarQuery.java @@ -0,0 +1,35 @@ +package org.ical4j.connector.dav.request; + +import net.fortuna.ical4j.model.Calendar; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.ParserConfigurationException; +import java.util.Arrays; + +public class CalendarQuery implements XmlSupport, XmlSerializable { + + private final String[] componentType; + + public CalendarQuery(String... componentType) { + this.componentType = componentType; + } + + public Element build() throws ParserConfigurationException { + var document = newXmlDocument(); +// DomUtil.setNamespaceAttribute(document.getDocumentElement(), "xmlns:d", "DAV:"); +// DomUtil.setNamespaceAttribute(document.getDocumentElement(), "xmlns:c", +// "urn:ietf:params:xml:ns:caldav"); + return toXml(document); + } + + @Override + public Element toXml(Document document) { + return newCalDavElement(document, CalDavPropertyName.PROPERTY_FILTER, + newComponentFilter(document, Calendar.VCALENDAR, + Arrays.stream(componentType).map(t -> + newComponentFilter(document, t)).toArray(Element[]::new))); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/EventQuery.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/EventQuery.java new file mode 100644 index 00000000..40d3c465 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/EventQuery.java @@ -0,0 +1,62 @@ +package org.ical4j.connector.dav.request; + +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.Component; +import net.fortuna.ical4j.model.DateTime; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; +import org.apache.jackrabbit.webdav.version.report.ReportInfo; +import org.ical4j.connector.dav.property.BaseDavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class EventQuery extends ReportInfo implements XmlSupport { + + private DateTime startTime; + + private DateTime endTime; + + public EventQuery(int depth) { + super(CalDavPropertyName.CALENDAR_QUERY, depth); + } + + public EventQuery withStartTime(DateTime startTime) { + this.startTime = startTime; + return this; + } + + public EventQuery withEndTime(DateTime endTime) { + this.endTime = endTime; + return this; + } + + @Override + public Element toXml(Document document) { + + var calData = newElement(document, CalDavPropertyName.CALENDAR_DATA); + + var calFilter = newComponentFilter(document, Calendar.VCALENDAR, + newComponentFilter(document, Component.VEVENT, + newTimeRange(document, startTime.toString(), endTime.toString()))); + + var property = newElement(document, BaseDavPropertyName.PROP, + newCalDavElement(document, DavConstants.PROPERTY_GETETAG), + document.importNode(calData, true)); + + document.appendChild(property); + setContentElement(property); + + var parentFilter = newCalDavElement(document, CalDavPropertyName.PROPERTY_FILTER); + setContentElement(parentFilter); + + var importedFilter = document.importNode(calFilter, true); + parentFilter.appendChild(importedFilter); + var propertyNames = new DavPropertyNameSet(); + propertyNames.add(DavPropertyName.create(DavPropertyName.XML_PROP, CalDavPropertyName.NAMESPACE)); + propertyNames.add(DavPropertyName.GETETAG); + + return super.toXml(document); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/ExpandPropertyQuery.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/ExpandPropertyQuery.java new file mode 100644 index 00000000..cd0e65b8 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/ExpandPropertyQuery.java @@ -0,0 +1,55 @@ +package org.ical4j.connector.dav.request; + +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.ical4j.connector.dav.property.CSDavPropertyName; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.ParserConfigurationException; +import java.util.ArrayList; +import java.util.List; + +public class ExpandPropertyQuery implements XmlSupport, XmlSerializable { + + public enum Type { + PROXY_READ_FOR(CSDavPropertyName.PROXY_READ_FOR), + PROXY_WRITE_FOR(CSDavPropertyName.PROXY_WRITE_FOR); + + private final DavPropertyName propertyName; + + Type(DavPropertyName propertyName) { + this.propertyName = propertyName; + } + + public DavPropertyName getPropertyName() { + return propertyName; + } + } + + private final Type type; + + private final List propertyNames; + + public ExpandPropertyQuery(Type type) { + this.type = type; + propertyNames = new ArrayList<>(); + } + + public ExpandPropertyQuery withPropertyName(DavPropertyName propertyName) { + propertyNames.add(propertyName); + return this; + } + + public Element build() throws ParserConfigurationException { + var document = newXmlDocument(); + return toXml(document); + } + + @Override + public Element toXml(Document document) { + var propertyElement = type.getPropertyName().toXml(document); + propertyNames.stream().map(p -> p.toXml(document)).forEach(propertyElement::appendChild); + return propertyElement; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/FreeBusyQuery.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/FreeBusyQuery.java new file mode 100644 index 00000000..6e1241a0 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/FreeBusyQuery.java @@ -0,0 +1,4 @@ +package org.ical4j.connector.dav.request; + +public class FreeBusyQuery { +} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/MkCalendar.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/MkCalendarEntity.java similarity index 80% rename from src/main/java/net/fortuna/ical4j/connector/dav/MkCalendar.java rename to ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/MkCalendarEntity.java index c96f61b1..17c6f28a 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/MkCalendar.java +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/MkCalendarEntity.java @@ -29,12 +29,12 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav; +package org.ical4j.connector.dav.request; -import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertySet; -import org.apache.jackrabbit.webdav.xml.DomUtil; import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.ical4j.connector.dav.property.CalDavPropertyName; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -46,7 +46,7 @@ * @author Ben * */ -public class MkCalendar implements XmlSerializable { +public class MkCalendarEntity implements XmlSerializable, XmlSupport { /** * @@ -69,16 +69,16 @@ public void setProperties(DavPropertySet properties) { this.properties = properties; } + public MkCalendarEntity withProperties(DavPropertySet properties) { + this.properties = properties; + return this; + } + /** * {@inheritDoc} */ public Element toXml(Document document) { - Element set = DomUtil.createElement(document, DavConstants.XML_SET, DavConstants.NAMESPACE); - set.appendChild(properties.toXml(document)); - - Element mkcalendar = DomUtil.createElement(document, XML_MKCALENDAR, CalDavConstants.CALDAV_NAMESPACE); - mkcalendar.appendChild(set); - return mkcalendar; + return newElement(document, XML_MKCALENDAR, CalDavPropertyName.NAMESPACE, + newElement(document, DavPropertyName.XML_SET, DavPropertyName.NAMESPACE, properties.toXml(document))); } - } diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/MkColEntity.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/MkColEntity.java new file mode 100644 index 00000000..ca88e8bf --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/MkColEntity.java @@ -0,0 +1,23 @@ +package org.ical4j.connector.dav.request; + +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class MkColEntity implements XmlSerializable, XmlSupport { + + private DavPropertySet properties; + + public MkColEntity withProperties(DavPropertySet properties) { + this.properties = properties; + return this; + } + + @Override + public Element toXml(Document document) { + return newElement(document, "create", DavPropertyName.NAMESPACE, + newElement(document, DavPropertyName.XML_SET, DavPropertyName.NAMESPACE, properties.toXml(document))); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/PrincipalMatch.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/PrincipalMatch.java new file mode 100644 index 00000000..89d12344 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/PrincipalMatch.java @@ -0,0 +1,4 @@ +package org.ical4j.connector.dav.request; + +public class PrincipalMatch { +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/PrincipalPropertySearch.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/PrincipalPropertySearch.java new file mode 100644 index 00000000..d02811e4 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/PrincipalPropertySearch.java @@ -0,0 +1,99 @@ +package org.ical4j.connector.dav.request; + +import net.fortuna.ical4j.model.parameter.CuType; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.security.SecurityConstants; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.ical4j.connector.dav.property.BaseDavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.ParserConfigurationException; + +public class PrincipalPropertySearch implements XmlSupport, XmlSerializable { + + private final CuType type; + + private final String nameToSearch; + + public PrincipalPropertySearch(CuType type) { + this(type, null); + } + + public PrincipalPropertySearch(CuType type, String nameToSearch) { + this.type = type; + this.nameToSearch = nameToSearch; + } + + protected Element propertiesForPropSearch(Document document) { + var firstNameProperty = newCsElement(document, "first-name"); + var recordTypeProperty = newCsElement(document, "record-type"); + var calUserAddressSetProperty = newElement(document, CalDavPropertyName.USER_ADDRESS_SET); + var lastNameProperty = newCsElement(document, "last-name"); + var principalUrlProperty = newElement(document, SecurityConstants.PRINCIPAL_URL); + var calUserTypeProperty = newElement(document, CalDavPropertyName.USER_TYPE); + var displayNameForProperty = newElement(document, DavPropertyName.DISPLAYNAME); + var emailAddressSetProperty = newCsElement(document, "email-address-set"); + + return newElement(document, BaseDavPropertyName.PROP, firstNameProperty, recordTypeProperty, + calUserAddressSetProperty, lastNameProperty, principalUrlProperty, calUserTypeProperty, + displayNameForProperty, emailAddressSetProperty); + } + + public Element build() throws ParserConfigurationException { + var document = newXmlDocument(); + return toXml(document); + } + + @Override + public Element toXml(Document document) { + Element displayName; + if (nameToSearch != null) { + displayName = newElement(document, DavPropertyName.DISPLAYNAME); + } else { + displayName = newCalDavElement(document, "calendar-user-type"); + } + var displayNameProperty = newElement(document, BaseDavPropertyName.PROP, displayName); + + var containsMatch = newDavElement(document, "match"); + if (nameToSearch != null) { + containsMatch.setAttribute("match-type", "contains"); + containsMatch.setTextContent(nameToSearch); + } else { + containsMatch.setAttribute("match-type", "equals"); + containsMatch.setTextContent(type.getValue()); + } + + var propertySearchDisplayName = newDavElement(document, "property-search", + displayNameProperty, containsMatch); + + var properties = propertiesForPropSearch(document); + + var principalPropSearch = newDavElement(document, "principal-property-search"); + principalPropSearch.setAttribute("type", type.getValue()); + principalPropSearch.setAttribute("test", "anyof"); + principalPropSearch.appendChild(propertySearchDisplayName); + if (nameToSearch != null) { + + var emailAddressSet = newCsElement(document, "email-address-set"); + + var emailSetProperty = newElement(document, BaseDavPropertyName.PROP, emailAddressSet); + + var startsWith = newDavElement(document, "match"); + startsWith.setAttribute("match-type", "starts-with"); + if (startsWith != null) { + startsWith.setTextContent(nameToSearch); + } + + var propertySearchEmail = newDavElement(document, "property-search"); + propertySearchEmail.setTextContent(nameToSearch); + propertySearchEmail.appendChild(emailSetProperty); + propertySearchEmail.appendChild(startsWith); + + principalPropSearch.appendChild(propertySearchEmail); + } + principalPropSearch.appendChild(properties); + return principalPropSearch; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/XmlSupport.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/XmlSupport.java new file mode 100644 index 00000000..41c016c5 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/request/XmlSupport.java @@ -0,0 +1,131 @@ +package org.ical4j.connector.dav.request; + +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.apache.jackrabbit.webdav.xml.Namespace; +import org.ical4j.connector.dav.property.CSDavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.StringWriter; +import java.util.Arrays; + +public interface XmlSupport { + + /** + * + */ + String PROPERTY_COMP_FILTER = "comp-filter"; + + /** + * + */ + String PROPERTY_TIME_RANGE = "time-range"; + + /** + * + */ + String ATTRIBUTE_NAME = "name"; + + /** + * + */ + String ATTRIBUTE_START = "start"; + + /** + * + */ + String ATTRIBUTE_END = "end"; + + default Document newXmlDocument(Node...children) throws ParserConfigurationException { + Document document = DomUtil.createDocument(); + Arrays.stream(children).forEach(document::appendChild); + + return document; + } + + default Element newElement(Document document, String elementName, Namespace namespace, Node...children) { + Element element = DomUtil.createElement(document, elementName, namespace); + Arrays.stream(children).forEach(element::appendChild); + return element; + } + + default Element newElement(Document document, DavPropertyName propertyName, Node...children) { + Element element = DomUtil.createElement(document, propertyName.getName(), propertyName.getNamespace()); + Arrays.stream(children).forEach(element::appendChild); + return element; + } + + /** + * + * @param document + * @param elementName + * @param children + * @return + * @deprecated use {@link XmlSupport#newElement(Document, DavPropertyName, Node...)} + */ + @Deprecated + default Element newDavElement(Document document, String elementName, Node...children) { + return newElement(document, elementName, DavConstants.NAMESPACE, children); + } + + /** + * + * @param document + * @param elementName + * @param children + * @return + * @deprecated use {@link XmlSupport#newElement(Document, DavPropertyName, Node...)} + */ + @Deprecated + default Element newCsElement(Document document, String elementName, Node...children) { + return newElement(document, elementName, CSDavPropertyName.NAMESPACE, children); + } + + /** + * + * @param document + * @param elementName + * @param children + * @return + * @deprecated use {@link XmlSupport#newElement(Document, DavPropertyName, Node...)} + */ + @Deprecated + default Element newCalDavElement(Document document, String elementName, Node...children) { + return newElement(document, elementName, CalDavPropertyName.NAMESPACE, children); + } + + default Element newComponentFilter(Document document, String componentName, Node...children) { + Element calFilter = newCalDavElement(document, PROPERTY_COMP_FILTER); + calFilter.setAttribute(ATTRIBUTE_NAME, componentName); + Arrays.stream(children).forEach(calFilter::appendChild); + return calFilter; + } + + default Element newTimeRange(Document document, String startTime, String endTime) { + Element timeRange = newCalDavElement(document, PROPERTY_TIME_RANGE); + timeRange.setAttribute(ATTRIBUTE_START, startTime); + timeRange.setAttribute(ATTRIBUTE_END, endTime); + return timeRange; + } + + static String toString(Element node) throws TransformerException { + TransformerFactory transFactory = TransformerFactory.newInstance(); + Transformer transformer = transFactory.newTransformer(); + StringWriter buffer = new StringWriter(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.transform(new DOMSource(node), + new StreamResult(buffer)); + return buffer.toString(); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/AbstractResponseHandler.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/AbstractResponseHandler.java new file mode 100644 index 00000000..c0728b1e --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/AbstractResponseHandler.java @@ -0,0 +1,89 @@ +package org.ical4j.connector.dav.response; + +import org.apache.http.*; +import org.apache.http.client.ResponseHandler; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.MultiStatus; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.ical4j.connector.MediaType; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +abstract class AbstractResponseHandler implements ResponseHandler { + + protected InputStream getContent(HttpResponse response, MediaType mediaType) throws IOException { + var httpEntity = response.getEntity(); + if (httpEntity != null && httpEntity.getContentType().getValue().startsWith(mediaType.getContentType())) { + return httpEntity.getContent(); + } + return null; + } + + protected List getHeaders(HttpResponse response, String name, Function mapper) { + return Arrays.stream(response.getHeaders(name)).map(mapper).collect(Collectors.toList()); + } + + protected List getHeaderElements(HttpResponse response, String name, Function mapper) { + return Arrays.stream(response.getHeaders(name)).flatMap(h -> Arrays.stream(h.getElements())).map(mapper) + .filter(Objects::nonNull).collect(Collectors.toList()); + } + + private Document getResponseBodyAsDocument(HttpEntity entity) throws IOException { + + if (entity == null) { + return null; + } else { + // read response and try to build a xml document + try (var in = entity.getContent()) { + return DomUtil.parseDocument(in); + } catch (ParserConfigurationException ex) { + throw new IOException("XML parser configuration error", ex); + } catch (SAXException ex) { + throw new IOException("XML parsing error", ex); + } + } + } + + private MultiStatus getResponseBodyAsMultiStatus(HttpResponse response) throws DavException { + try { + var doc = getResponseBodyAsDocument(response.getEntity()); + if (doc == null) { + throw new DavException(response.getStatusLine().getStatusCode(), "no response body"); + } + return MultiStatus.createFromXml(doc.getDocumentElement()); + } catch (IOException ex) { + throw new DavException(response.getStatusLine().getStatusCode(), ex); + } + } + + protected MultiStatus getMultiStatus(HttpResponse response) throws DavException { + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS) { + throw new RuntimeException("Unexpected status code: " + response.getStatusLine().getStatusCode()); + } + return getResponseBodyAsMultiStatus(response); + } + + protected String toString(Document document) throws TransformerException, IOException { + var domSource = new DOMSource(document); + var writer = new StringWriter(); + var result = new StreamResult(writer); + var tf = TransformerFactory.newInstance(); + var transformer = tf.newTransformer(); + transformer.transform(domSource, result); + return writer.toString(); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalDavCollections.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalDavCollections.java new file mode 100644 index 00000000..9d5dd1f9 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalDavCollections.java @@ -0,0 +1,131 @@ +package org.ical4j.connector.dav.response; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.property.DavProperty; +import org.apache.jackrabbit.webdav.property.DefaultDavProperty; +import org.apache.jackrabbit.webdav.security.SecurityConstants; +import org.ical4j.connector.dav.CalDavCalendarCollection; +import org.ical4j.connector.dav.property.CSDavPropertyName; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GetCalDavCollections extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) throws ClientProtocolException, IOException { + List collections = new ArrayList(); + try { + var multiStatus = getMultiStatus(response); + var responses = multiStatus.getResponses(); + for (var msr : responses) { + var properties = msr.getProperties(DavServletResponse.SC_OK); + DavProperty writeForProperty = properties.get(CSDavPropertyName.PROPERTY_PROXY_WRITE_FOR, + CSDavPropertyName.NAMESPACE); + List writeCollections = getDelegateCollections(writeForProperty); + for (var writeCollection : writeCollections) { + writeCollection.setReadOnly(false); + collections.add(writeCollection); + } + DavProperty readForProperty = properties.get(CSDavPropertyName.PROPERTY_PROXY_READ_FOR, + CSDavPropertyName.NAMESPACE); + List readCollections = getDelegateCollections(readForProperty); + for (var readCollection : readCollections) { + readCollection.setReadOnly(true); + collections.add(readCollection); + } + } + } catch (DavException | ParserConfigurationException e) { + throw new RuntimeException(e); + } + return collections; + } + + @SuppressWarnings("unchecked") + protected List getDelegateCollections(DavProperty proxyDavProperty) + throws ParserConfigurationException, IOException, DavException { + + List delegatedCollections = new ArrayList(); + + /* + * Zimbra check: Zimbra advertise calendar-proxy, but it will return 404 in propstat if Enable delegation for + * Apple iCal CalDAV client is not enabled + */ + if (proxyDavProperty != null) { + var propertyValue = proxyDavProperty.getValue(); + List response; + + if (propertyValue instanceof List) { + response = (List) proxyDavProperty.getValue(); + if (response != null) { + for (var objectInArray: response) { + if (objectInArray instanceof Element) { + DavProperty newProperty = DefaultDavProperty + .createFromXml((Element) objectInArray); + if ((newProperty.getName().getName().equals((DavConstants.XML_RESPONSE))) + && (newProperty.getName().getNamespace().equals(DavConstants.NAMESPACE))) { + List responseChilds = (List) newProperty.getValue(); + for (var responseChild : responseChilds) { + if (responseChild instanceof Element) { + DavProperty responseChildElement = DefaultDavProperty + .createFromXml((Element) responseChild); + if (responseChildElement.getName().getName().equals(DavConstants.XML_PROPSTAT)) { + List propStatChilds = (List) responseChildElement + .getValue(); + for (var propStatChild : propStatChilds) { + if (propStatChild instanceof Element) { + DavProperty propStatChildElement = DefaultDavProperty + .createFromXml((Element) propStatChild); + if (propStatChildElement.getName().getName() + .equals(DavConstants.XML_PROP)) { + List propChilds = (List) propStatChildElement + .getValue(); + for (var propChild : propChilds) { + if (propChild instanceof Element) { + DavProperty propChildElement = DefaultDavProperty + .createFromXml((Element) propChild); + if (propChildElement.getName().equals( + SecurityConstants.PRINCIPAL_URL)) { + List principalUrlChilds = (List) propChildElement + .getValue(); + for (var principalUrlChild : principalUrlChilds) { + if (principalUrlChild instanceof Element) { + DavProperty principalUrlElement = DefaultDavProperty + .createFromXml((Element) principalUrlChild); + if (principalUrlElement.getName().getName() + .equals(DavConstants.XML_HREF)) { + var principalsUri = (String) principalUrlElement + .getValue(); + //XXX: Need to reimplement.. +// String urlForcalendarHomeSet = findCalendarHomeSet(getHostURL() +// + principalsUri); +// delegatedCollections.addAll(getCollectionsForHomeSet(this,urlForcalendarHomeSet)); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return delegatedCollections; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalendarData.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalendarData.java new file mode 100644 index 00000000..0cb7991d --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalendarData.java @@ -0,0 +1,37 @@ +package org.ical4j.connector.dav.response; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.Calendar; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavException; +import org.ical4j.connector.dav.property.CalDavPropertyName; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class GetCalendarData extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) { + try { + var multiStatus = getMultiStatus(response); + return Arrays.stream(multiStatus.getResponses()) + .filter(msr -> msr.getProperties(HttpStatus.SC_OK).get(CalDavPropertyName.CALENDAR_DATA) != null) + .map(msr -> { + try { + return new CalendarBuilder().build( + new StringReader((String) msr.getProperties(HttpStatus.SC_OK).get(CalDavPropertyName.CALENDAR_DATA).getValue())); + } catch (IOException | ParserException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } catch (DavException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalendarResource.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalendarResource.java new file mode 100644 index 00000000..5f280d06 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCalendarResource.java @@ -0,0 +1,26 @@ +package org.ical4j.connector.dav.response; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.Calendar; +import org.apache.http.HttpResponse; +import org.ical4j.connector.MediaType; + +import java.io.IOException; + +public class GetCalendarResource extends AbstractResponseHandler { + + @Override + public Calendar handleResponse(HttpResponse response) throws IOException { + var content = getContent(response, MediaType.ICALENDAR_2_0); + if (content != null) { + try { + var builder = new CalendarBuilder(); + return builder.build(content); + } catch (ParserException e) { + throw new RuntimeException(e); + } + } + return null; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCardDavCollections.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCardDavCollections.java new file mode 100644 index 00000000..8eb4e501 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCardDavCollections.java @@ -0,0 +1,124 @@ +package org.ical4j.connector.dav.response; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.property.DavProperty; +import org.apache.jackrabbit.webdav.property.DefaultDavProperty; +import org.apache.jackrabbit.webdav.security.SecurityConstants; +import org.ical4j.connector.dav.CardDavCollection; +import org.ical4j.connector.dav.property.CSDavPropertyName; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GetCardDavCollections extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) throws ClientProtocolException, IOException { + List collections = new ArrayList(); + + try { + var multiStatus = getMultiStatus(response); + var responses = multiStatus.getResponses(); + for (var msr : responses) { + var properties = msr.getProperties(DavServletResponse.SC_OK); + DavProperty writeForProperty = properties.get(CSDavPropertyName.PROPERTY_PROXY_WRITE_FOR, + CSDavPropertyName.NAMESPACE); + collections.addAll(getDelegateCollections(writeForProperty)); + DavProperty readForProperty = properties.get(CSDavPropertyName.PROPERTY_PROXY_READ_FOR, + CSDavPropertyName.NAMESPACE); + collections.addAll(getDelegateCollections(readForProperty)); + } + } catch (DavException | ParserConfigurationException e) { + throw new RuntimeException(e); + } + return collections; + } + + protected List getDelegateCollections(DavProperty proxyDavProperty) + throws ParserConfigurationException, IOException, DavException { + /* + * Zimbra check: Zimbra advertise calendar-proxy, but it will return 404 in propstat if Enable delegation for + * Apple iCal CardDav client is not enabled + */ + if (proxyDavProperty != null) { + var propertyValue = proxyDavProperty.getValue(); + List response; + + if (propertyValue instanceof List) { + response = (List) proxyDavProperty.getValue(); + if (response != null) { + for (var objectInArray : response) { + if (objectInArray instanceof Element) { + DavProperty newProperty = DefaultDavProperty + .createFromXml((Element) objectInArray); + if ((newProperty.getName().getName().equals((DavConstants.XML_RESPONSE))) + && (newProperty.getName().getNamespace().equals(DavConstants.NAMESPACE))) { + List responseChilds = (List) newProperty.getValue(); + for (var responseChild : responseChilds) { + if (responseChild instanceof Element) { + DavProperty responseChildElement = DefaultDavProperty + .createFromXml((Element) responseChild); + if (responseChildElement.getName().getName().equals(DavConstants.XML_PROPSTAT)) { + List propStatChilds = (List) responseChildElement + .getValue(); + for (var propStatChild : propStatChilds) { + if (propStatChild instanceof Element) { + DavProperty propStatChildElement = DefaultDavProperty + .createFromXml((Element) propStatChild); + if (propStatChildElement.getName().getName() + .equals(DavConstants.XML_PROP)) { + List propChilds = (List) propStatChildElement + .getValue(); + for (var propChild : propChilds) { + if (propChild instanceof Element) { + DavProperty propChildElement = DefaultDavProperty + .createFromXml((Element) propChild); + if (propChildElement.getName().equals( + SecurityConstants.PRINCIPAL_URL)) { + List principalUrlChilds = (List) propChildElement + .getValue(); + for (var principalUrlChild : principalUrlChilds) { + if (principalUrlChild instanceof Element) { + DavProperty principalUrlElement = DefaultDavProperty + .createFromXml((Element) principalUrlChild); + if (principalUrlElement.getName().getName() + .equals(DavConstants.XML_HREF)) { + var principalsUri = (String) principalUrlElement + .getValue(); + //XXX: need to reimplement +// String urlForcalendarHomeSet = findAddressBookHomeSet(getHostURL() +// + principalsUri); +// return getCollectionsForHomeSet(this, +// urlForcalendarHomeSet); + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } else if (propertyValue instanceof Element) { + System.out.println(((Element)propertyValue).getNodeName()); + System.out.println(((Element)propertyValue).getChildNodes()); + } + } + return new ArrayList<>(); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCollections.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCollections.java new file mode 100644 index 00000000..f1af7fd2 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetCollections.java @@ -0,0 +1,41 @@ +package org.ical4j.connector.dav.response; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.ical4j.connector.dav.ResourceType; +import org.w3c.dom.Element; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Handle a DAV response by extracting identified collections and their associated properties. + */ +public class GetCollections extends AbstractResponseHandler> { + + private final List resourceTypes; + + public GetCollections(ResourceType...type) { + this.resourceTypes = Arrays.stream(type).map(ResourceType::description).collect(Collectors.toList()); + } + + @Override + public Map handleResponse(HttpResponse response) { + try { + var multiStatus = getMultiStatus(response); + return Arrays.stream(multiStatus.getResponses()) + .filter(msr -> resourceTypes.containsAll( + (List) msr.getProperties(HttpStatus.SC_OK).get(DavPropertyName.RESOURCETYPE).getValue())) + .collect(Collectors.toMap(MultiStatusResponse::getHref, + msr -> msr.getProperties(HttpStatus.SC_OK))); + } catch (DavException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetFreeBusyData.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetFreeBusyData.java new file mode 100644 index 00000000..093238e6 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetFreeBusyData.java @@ -0,0 +1,34 @@ +package org.ical4j.connector.dav.response; + +import net.fortuna.ical4j.data.ParserException; +import org.apache.http.HttpResponse; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.ical4j.connector.dav.ScheduleResponse; +import org.ical4j.connector.dav.property.CalDavPropertyName; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GetFreeBusyData extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) throws IOException { + List responses = new ArrayList<>(); + try { + var xmlDoc = DomUtil.parseDocument(response.getEntity().getContent()); + var nodes = xmlDoc.getElementsByTagNameNS(CalDavPropertyName.NAMESPACE.getURI(), + DavPropertyName.XML_RESPONSE); + for (int nodeItr = 0; nodeItr < nodes.getLength(); nodeItr++) { + responses.add(new ScheduleResponse((Element) nodes.item(nodeItr))); + } + } catch (ParserConfigurationException | SAXException | ParserException e) { + throw new RuntimeException(e); + } + return responses; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetPrincipals.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetPrincipals.java new file mode 100644 index 00000000..03312ede --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetPrincipals.java @@ -0,0 +1,96 @@ +package org.ical4j.connector.dav.response; + +import net.fortuna.ical4j.model.parameter.Cn; +import net.fortuna.ical4j.model.parameter.CuType; +import net.fortuna.ical4j.model.property.Attendee; +import org.apache.http.HttpResponse; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.property.DavProperty; +import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.ical4j.connector.dav.property.CSDavPropertyName; +import org.ical4j.connector.dav.property.CalDavPropertyName; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +public class GetPrincipals extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) throws IOException { + List resources = new ArrayList<>(); + + try { + var multiStatus = getMultiStatus(response); + var responses = multiStatus.getResponses(); + for (var msr : responses) { + + var resource = new Attendee(); + var propertiesInResponse = msr.getProperties(DavServletResponse.SC_OK); + + DavProperty displayNameFromResponse = propertiesInResponse.get("displayname", + DavConstants.NAMESPACE); + if ((displayNameFromResponse != null) && (displayNameFromResponse.getValue() != null)) { + resource.add(new Cn((String) displayNameFromResponse.getValue())); + } + + var calAddressUri = getCalAddress(propertiesInResponse); + if (calAddressUri != null) { + resource.setCalAddress(calAddressUri); + } + + DavProperty calendarUserType = propertiesInResponse.get(CalDavPropertyName.USER_TYPE); + if ((calendarUserType != null) && (calendarUserType.getValue() != null)) { + resource.add(new CuType((String) calendarUserType.getValue())); + } + + resources.add(resource); + } + } catch (DavException e) { + throw new RuntimeException(e); + } + return resources; + } + + private URI getCalAddress(DavPropertySet propertiesInResponse) { + DavProperty emailSet = propertiesInResponse.get("email-address-set", + CSDavPropertyName.NAMESPACE); + + if (emailSet != null && emailSet.getValue() != null) { + var emailSetValue = emailSet.getValue(); + if (emailSetValue instanceof List) { + for (var email: (List)emailSetValue) { + if (email instanceof org.w3c.dom.Node) { + var emailAddress = ((org.w3c.dom.Node)email).getTextContent(); + if (emailAddress != null && emailAddress.trim().length() > 0) { + if (!emailAddress.startsWith("mailto:")) { + emailAddress = "mailto:".concat(emailAddress); + } + return URI.create(emailAddress); + } + } + } + } + } else { + DavProperty calendarUserAddressSet = propertiesInResponse.get(CalDavPropertyName.PROPERTY_USER_ADDRESS_SET, + CalDavPropertyName.NAMESPACE); + if (calendarUserAddressSet != null && calendarUserAddressSet.getValue() != null) { + var value = calendarUserAddressSet.getValue(); + if (value instanceof List) { + for (var addressSet: (List)value) { + if (addressSet instanceof org.w3c.dom.Node) { + var url = ((org.w3c.dom.Node)addressSet).getTextContent(); + if (url.startsWith("urn:uuid")) { + return URI.create(url); + } + } + } + } + } + } + return null; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetPropertyValue.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetPropertyValue.java new file mode 100644 index 00000000..e18ebc77 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetPropertyValue.java @@ -0,0 +1,28 @@ +package org.ical4j.connector.dav.response; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.ClientProtocolException; +import org.apache.jackrabbit.webdav.DavException; +import org.w3c.dom.Element; + +import java.io.IOException; + +public class GetPropertyValue extends AbstractResponseHandler { + + @Override + public T handleResponse(HttpResponse httpResponse) throws ClientProtocolException, IOException { + try { + var multiStatus = getMultiStatus(httpResponse); + for (var msr : multiStatus.getResponses()) { + // only one response expected.. return found properties + var propElement = (Element) msr.getProperties(HttpStatus.SC_OK).iterator().nextProperty().getValue(); + //noinspection unchecked + return (T) propElement.getFirstChild().getNodeValue(); + } + } catch (DavException e) { + throw new RuntimeException(e); + } + return null; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetResourceProperties.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetResourceProperties.java new file mode 100644 index 00000000..0807d1a3 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetResourceProperties.java @@ -0,0 +1,23 @@ +package org.ical4j.connector.dav.response; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavException; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class GetResourceProperties extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) { + try { + var multiStatus = getMultiStatus(response); + return Arrays.stream(multiStatus.getResponses()).map(msr -> + new ResourceProps(msr.getHref(), msr.getProperties(HttpStatus.SC_OK))).collect(Collectors.toList()); + } catch (DavException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetSupportedFeatures.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetSupportedFeatures.java new file mode 100644 index 00000000..2a537e13 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetSupportedFeatures.java @@ -0,0 +1,19 @@ +package org.ical4j.connector.dav.response; + +import org.apache.http.HttpResponse; +import org.apache.jackrabbit.webdav.DavConstants; +import org.ical4j.connector.dav.SupportedFeature; + +import java.util.List; + +public class GetSupportedFeatures extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) { + if (response.getStatusLine().getStatusCode() > 299) { + throw new RuntimeException("Method failed: " + response.getStatusLine()); + } + return getHeaderElements(response, DavConstants.HEADER_DAV, + header -> SupportedFeature.findByDescription(header.getName())); + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetVCardData.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetVCardData.java new file mode 100644 index 00000000..cf735efc --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetVCardData.java @@ -0,0 +1,37 @@ +package org.ical4j.connector.dav.response; + +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.VCardBuilder; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavException; +import org.ical4j.connector.dav.property.CardDavPropertyName; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class GetVCardData extends AbstractResponseHandler> { + + @Override + public List handleResponse(HttpResponse response) { + try { + var multiStatus = getMultiStatus(response); + return Arrays.stream(multiStatus.getResponses()) + .filter(msr -> msr.getProperties(HttpStatus.SC_OK).get(CardDavPropertyName.ADDRESS_DATA) != null) + .map(msr -> { + String value = (String) msr.getProperties(HttpStatus.SC_OK).get(CardDavPropertyName.ADDRESS_DATA).getValue(); + try { + return new VCardBuilder(new StringReader(value)).build(); + } catch (IOException | ParserException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } catch (DavException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetVCardResource.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetVCardResource.java new file mode 100644 index 00000000..c98ae45f --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/GetVCardResource.java @@ -0,0 +1,26 @@ +package org.ical4j.connector.dav.response; + +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.VCardBuilder; +import org.apache.http.HttpResponse; +import org.ical4j.connector.MediaType; + +import java.io.IOException; + +public class GetVCardResource extends AbstractResponseHandler { + + @Override + public VCard handleResponse(HttpResponse response) throws IOException { + var content = getContent(response, MediaType.VCARD_4_0); + if (content != null) { + try { + var builder = new VCardBuilder(content); + return builder.build(); + } catch (ParserException e) { + throw new RuntimeException(e); + } + } + return null; + } +} diff --git a/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/ResourceProps.java b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/ResourceProps.java new file mode 100644 index 00000000..60c00c72 --- /dev/null +++ b/ical4j-connector-dav/src/main/java/org/ical4j/connector/dav/response/ResourceProps.java @@ -0,0 +1,23 @@ +package org.ical4j.connector.dav.response; + +import org.apache.jackrabbit.webdav.property.DavPropertySet; + +public class ResourceProps { + + private final String href; + + private final DavPropertySet properties; + + public ResourceProps(String href, DavPropertySet properties) { + this.href = href; + this.properties = properties; + } + + public String getHref() { + return href; + } + + public DavPropertySet getProperties() { + return properties; + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractCalDavResourceIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractCalDavResourceIntegrationTest.groovy new file mode 100644 index 00000000..1f13d8c9 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractCalDavResourceIntegrationTest.groovy @@ -0,0 +1,31 @@ +package org.ical4j.connector.dav + +import org.apache.http.client.CredentialsProvider +import org.apache.jackrabbit.webdav.property.DavPropertySet + +abstract class AbstractCalDavResourceIntegrationTest extends AbstractIntegrationTest { + + abstract String getPathPrefix(); + + abstract PathResolver getPathResolver(); + + abstract CredentialsProvider getCredentialsProvider() + + def 'test get property names'() { + given: 'a dav client' + CalDavLocatorFactory locatorFactory = [getPathPrefix(), getPathResolver()] + def client = new DefaultDavClient(getContainerUrl(), new DavClientConfiguration()) + client.begin(getCredentialsProvider()) + + and: 'a caldav resource' + CalDavResourceFactory resourceFactory = [] + CalDavResource resource = [resourceFactory, locatorFactory.createResourceLocator('', getContainerUrl() + '/newcol'), + new DavPropertySet(), client, null] + + when: 'property names are requested' + def propertyNames = resource.getPropertyNames() + + then: 'the result is as expected' + propertyNames.length > 0 + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractCalendarStoreIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractCalendarStoreIntegrationTest.groovy new file mode 100644 index 00000000..258c7d15 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractCalendarStoreIntegrationTest.groovy @@ -0,0 +1,53 @@ +package org.ical4j.connector.dav + + +import spock.lang.Ignore + +abstract class AbstractCalendarStoreIntegrationTest extends AbstractIntegrationTest { + + def 'test find calendar home set'() { + given: 'an object store' + def store = new CalDavCalendarStore('ical4j-connector', URI.create(getContainerUrl()).toURL(), + getPathResolver()) + + and: 'a connection is established' + store.connect(new DavSessionConfiguration().withCredentialsProvider(getCredentialsProvider()) + .withUser(getUser()).withWorkspace(getWorkspace())) + + and: 'a collection is created' + def collection = store.addCollection('testCollection5') + + when: 'calendar home set it requested' + def calendarHomeSet = store.findCalendarHomeSet() + + then: 'the calendar home set is retrieved' + calendarHomeSet == expectedValues['calendar-home-set'] + + cleanup: + collection.delete() + } + + @Ignore('not working for radicale and baikal') + def 'test collection creation'() { + given: 'an object store' + def store = new CalDavCalendarStore('ical4j-connector', URI.create(getContainerUrl()).toURL(), + getPathResolver()) + + and: 'a connection is established' + store.connect(new DavSessionConfiguration().withCredentialsProvider(getCredentialsProvider()) + .withUser(getUser()).withWorkspace(getWorkspace())) + + when: 'a new collection is added' + def collection = store.addCollection('test1') + def collection2 = store.addCollection('test2') + + then: 'the collection is created' + collection != null + + and: 'collections size matches expected' + store.getCollections().size() == 1 + + cleanup: + collection.delete() + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractDavClientIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractDavClientIntegrationTest.groovy new file mode 100644 index 00000000..08755c02 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractDavClientIntegrationTest.groovy @@ -0,0 +1,218 @@ +package org.ical4j.connector.dav + + +import net.fortuna.ical4j.model.ContentBuilder +import net.fortuna.ical4j.model.property.DtStart +import org.apache.http.impl.client.BasicAuthCache +import org.apache.jackrabbit.webdav.DavException +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet +import org.apache.jackrabbit.webdav.property.DavPropertySet +import org.ical4j.connector.dav.property.DavPropertyBuilder +import org.ical4j.connector.dav.request.CalendarQuery +import spock.lang.Ignore +import spock.lang.Shared + +import java.time.LocalDateTime + +import static org.apache.jackrabbit.webdav.property.DavPropertyName.* +import static org.apache.jackrabbit.webdav.security.SecurityConstants.* +import static org.ical4j.connector.dav.property.BaseDavPropertyName.CURRENT_USER_PRINCIPAL +import static org.ical4j.connector.dav.property.BaseDavPropertyName.SUPPORTED_REPORT_SET +import static org.ical4j.connector.dav.property.CalDavPropertyName.* +import static org.ical4j.connector.dav.property.CardDavPropertyName.ADDRESSBOOK_HOME_SET + +abstract class AbstractDavClientIntegrationTest extends AbstractIntegrationTest { + + @Shared + CalDavSupport client + + def setupSpec() { + def clientFactory = new DavClientFactory().withPreemptiveAuth(true) + def href = URI.create(getContainerUrl()).toURL() + def client = clientFactory.newInstance(href); + client.begin(getCredentialsProvider()) + this.client = client; + } + + def 'assert preemptive auth configuration'() { + given: 'a dav client factory configured for preemptive auth' + def clientFactory = new DavClientFactory().withPreemptiveAuth(true) + + when: 'a new client instance is created' + def href = URI.create(getContainerUrl()).toURL() + def client = clientFactory.newInstance(href); + + and: 'a session is initiated' + client.begin(getCredentialsProvider()) + + then: 'the client is configured for preemptive auth' + client.httpClientContext.authCache instanceof BasicAuthCache + } + + def 'test client authentication'() { + given: 'a dav client instance' + def href = URI.create(getContainerUrl()).toURL() + def client = new DavClientFactory().withPreemptiveAuth(true) + .withFollowRedirects(true).withCredentialsProvider(getCredentialsProvider()) + .newInstance(href) + + when: 'a session is started' +// client.begin(getCredentialsProvider()) + def supportedFeatures= client.getSupportedFeatures() + + then: 'authentication is successful' + supportedFeatures == expectedValues['supported-features'] + } + + def 'test create collection'() { + given: 'a dav client instance' + def href = URI.create(getContainerUrl()).toURL() + def client = new DavClientFactory().withPreemptiveAuth(true) + .newInstance(href) + + and: 'a resource path' + def path = getPathResolver().getCalendarPath('newcol', getWorkspace()) + + when: 'a session is started' + client.begin(getCredentialsProvider()) + + and: 'a new collection is created' + DavPropertySet props = [] + props.add(new DavPropertyBuilder<>().name(DISPLAYNAME).value('New Collection').build()) + props.add(new DavPropertyBuilder<>().name(CALENDAR_DESCRIPTION).value('A simple mkcalendar test').build()) + client.mkCalendar(path, props) + + then: 'the collection exists' + client.propFind(path, props.propertyNames).get(0).getProperties().asList() == props.asList() + + cleanup: 'remove collection' + client.delete(path) + } + + def 'test create invalid collection'() { + given: 'a dav client instance' + def href = URI.create(getContainerUrl()).toURL() + def client = new DavClientFactory().withPreemptiveAuth(true) + .newInstance(href) + + and: 'a non-existent resource path' + def path = getPathResolver().getCalendarPath('test', 'notexist') + + when: 'a session is started' + client.begin(getCredentialsProvider()) + + and: 'a new collection is created' + DavPropertySet props = [] + props.add(new DavPropertyBuilder<>().name(DISPLAYNAME).value('Test Collection').build()) + props.add(new DavPropertyBuilder<>().name(CALENDAR_DESCRIPTION).value('A simple mkcalendar test').build()) + client.mkCalendar(path, props) + + then: 'exception is thrown' + thrown(DavException) + } + + @Ignore('not working for radicale') + def 'test get collection'() { + given: 'a dav client instance' + def href = URI.create(getContainerUrl()).toURL() + def client = new DavClientFactory().withPreemptiveAuth(true) + .newInstance(href) + + and: 'a resource path' + def path = getPathResolver().getCalendarPath('newcol', getWorkspace()) + + when: 'a session is started' + client.begin(getCredentialsProvider()) + + and: 'a new collection is created' + DavPropertySet props = [] + props.add(new DavPropertyBuilder<>().name(DISPLAYNAME).value('Test Collection').build()) + props.add(new DavPropertyBuilder<>().name(CALENDAR_DESCRIPTION).value('A simple mkcalendar test').build()) + client.mkCalendar(path, props) + + then: 'the retrieved calendar props are as expected' + def result = client.propFind(path, DISPLAYNAME) + result.get(0).getProperties().get(DISPLAYNAME).value == 'Test Collection' + + and: 'when calendar is added' + client.put(path + '/test.ics', new ContentBuilder().calendar { + vevent { + summary 'test' + dtstart new DtStart<>(LocalDateTime.now()) + } + }.withDefaults(), null); + + then: 'calendar details are retrievable' + CalendarQuery query = [] + def result2 = client.report(path, query, CALENDAR_DATA, GETETAG) + !result2.isEmpty() + + cleanup: 'remove collection' + client.delete(path) + } + + @Ignore('ignore for now until logic can be improved') + def 'test propfind all'() { + given: 'a dav client instance' + def href = URI.create(getContainerUrl()).toURL() + def client = new DavClientFactory().withPreemptiveAuth(true) + .newInstance(href) + + and: 'a resource path' + def path = getPathResolver().getCalendarPath('newcol', getWorkspace()) + + when: 'a session is started' + client.begin(getCredentialsProvider()) + + and: 'a new collection is created' + DavPropertySet props = [] + props.add(new DavPropertyBuilder<>().name(DISPLAYNAME).value('New Collection').build()) + props.add(new DavPropertyBuilder<>().name(CALENDAR_DESCRIPTION).value('A simple mkcalendar test').build()) + client.mkCalendar(path, props) + + and: 'expected props is defined' + DavPropertyNameSet propNames = [] + propNames.add(PRINCIPAL_COLLECTION_SET) + propNames.add(CURRENT_USER_PRIVILEGE_SET) + propNames.add(CALENDAR_HOME_SET) + propNames.add(CURRENT_USER_PRINCIPAL) + propNames.add(PRINCIPAL_URL) + propNames.add(OWNER) + propNames.add(ADDRESSBOOK_HOME_SET) + propNames.add(SUPPORTED_REPORT_SET) + propNames.add(USER_ADDRESS_SET) + propNames.add(RESOURCETYPE) + + then: 'propfind type = all is as expected' + propNames.containsAll client.propFindAll(path).propertyNames + + cleanup: 'remove collection' + client.delete(path) + } + + @Ignore + def 'test propfind principal collection set'() { + given: 'a dav client instance' + def href = URI.create(getContainerUrl()).toURL() + def client = new DavClientFactory().newInstance(href) + + and: 'a resource path' + def path = getPathResolver().getCalendarPath('default', getWorkspace()) + + when: 'a session is started' + client.begin(getCredentialsProvider()) + + and: 'a new collection is created' + DavPropertySet props = [] +// props.add(new DefaultDavProperty<>(DavPropertyName.DISPLAYNAME, 'Test Collection')) +// props.add(new DefaultDavProperty<>(CalDavPropertyName.CALENDAR_DESCRIPTION, 'A simple mkcalendar test')) +// client.mkCalendar('admin/test', props) + + and: 'expected props is defined' + props = [] + props.add(new DavPropertyBuilder().name(PRINCIPAL_COLLECTION_SET).value(new String[0]).build()) + + then: 'propfind type = all is as expected' + props.containsAll client.propFind(path, PRINCIPAL_COLLECTION_SET) + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractIntegrationTest.groovy new file mode 100644 index 00000000..967a6bb5 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/AbstractIntegrationTest.groovy @@ -0,0 +1,45 @@ +package org.ical4j.connector.dav + +import org.apache.http.client.CredentialsProvider +import org.testcontainers.containers.BindMode +import org.testcontainers.containers.GenericContainer +import org.testcontainers.spock.Testcontainers +import spock.lang.Shared +import spock.lang.Specification + +@Testcontainers +abstract class AbstractIntegrationTest extends Specification { + + @Shared + def container = new GenericContainer(getContainerImageName()) + .withExposedPorts(getContainerPort()).with(container -> { + getBindMounts().forEach { container.withFileSystemBind(it.v1, it.v2, it.v3)} + container + }) + + def setupSpec() { + container.start() + } + + abstract String getContainerImageName(); + + abstract int getContainerPort(); + + abstract List> getBindMounts(); + + abstract String getRepositoryPath(); + + String getContainerUrl() { + "http://$container.containerIpAddress:${container.getMappedPort(getContainerPort())}${getRepositoryPath()}" + } + + abstract PathResolver getPathResolver(); + + abstract CredentialsProvider getCredentialsProvider() + + abstract String getUser() + + abstract String getWorkspace() + + abstract Map getExpectedValues() +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalCalendarStoreIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalCalendarStoreIntegrationTest.groovy new file mode 100644 index 00000000..d0832e20 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalCalendarStoreIntegrationTest.groovy @@ -0,0 +1,4 @@ +package org.ical4j.connector.dav + +class BaikalCalendarStoreIntegrationTest extends AbstractCalendarStoreIntegrationTest implements BaikalTestSupport { +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalDavClientIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalDavClientIntegrationTest.groovy new file mode 100644 index 00000000..0f069c5d --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalDavClientIntegrationTest.groovy @@ -0,0 +1,4 @@ +package org.ical4j.connector.dav + +class BaikalDavClientIntegrationTest extends AbstractDavClientIntegrationTest implements BaikalTestSupport { +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalIntegrationTest.groovy new file mode 100644 index 00000000..01b2eb85 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalIntegrationTest.groovy @@ -0,0 +1,17 @@ +package org.ical4j.connector.dav + +class BaikalIntegrationTest extends AbstractIntegrationTest implements BaikalTestSupport { + + def 'test client authentication'() { + given: 'a dav client instance' + def href = URI.create(getContainerUrl()).toURL() + def client = new DavClientFactory().withPreemptiveAuth(true) + .withFollowRedirects(true).newInstance(href) + + when: 'a session is started' + def supportedFeatures = client.begin(getCredentialsProvider()) + + then: 'authentication is successful' + client.getSupportedFeatures() == expectedValues['supported-features'] + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalTestSupport.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalTestSupport.groovy new file mode 100644 index 00000000..18226b1e --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BaikalTestSupport.groovy @@ -0,0 +1,47 @@ +package org.ical4j.connector.dav + +import org.apache.http.auth.AuthScope +import org.apache.http.auth.UsernamePasswordCredentials +import org.apache.http.client.CredentialsProvider +import org.apache.http.impl.client.BasicCredentialsProvider +import org.testcontainers.containers.BindMode + +import static org.ical4j.connector.dav.SupportedFeature.* + +interface BaikalTestSupport { + + default String getContainerImageName() { 'ckulka/baikal:nginx' } + + default int getContainerPort() { 80 } + + default List> getBindMounts() { + [ + Tuple.tuple('./src/test/resources/baikal/config', '/var/www/baikal/config', BindMode.READ_WRITE), + Tuple.tuple('./src/test/resources/baikal/Specific', '/var/www/baikal/Specific', BindMode.READ_WRITE) + ] + } + + default String getRepositoryPath() { '/dav.php' } + + default PathResolver getPathResolver() { PathResolver.Defaults.BAIKAL } + + default String getUser() { 'test' } + + default String getWorkspace() { 'test' } + + default CredentialsProvider getCredentialsProvider() { + def credentials = new UsernamePasswordCredentials(getUser(), 'test'); + def credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, credentials); + credentialsProvider + } + + default Map getExpectedValues() { + [ + 'calendar-home-set': '/dav.php/calendars/test/', + 'supported-features': [EXTENDED_MKCOL, ACCESS_CONTROL, CALENDARSERVER_PRINCIPAL_PROPERTY_SEARCH, + CALENDAR_ACCESS, CALENDAR_PROXY, CALENDAR_AUTO_SCHEDULE, CALENDAR_AVAILABILITY, + CALENDARSERVER_SHARING, ADDRESSBOOK] + ] + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkCalendarStoreIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkCalendarStoreIntegrationTest.groovy new file mode 100644 index 00000000..6e4fa820 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkCalendarStoreIntegrationTest.groovy @@ -0,0 +1,7 @@ +package org.ical4j.connector.dav + +import spock.lang.Ignore + +@Ignore('container not starting correctly') +class BedeworkCalendarStoreIntegrationTest extends AbstractCalendarStoreIntegrationTest implements BedeworkTestSupport { +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkIntegrationTest.groovy new file mode 100644 index 00000000..9cfbbb67 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkIntegrationTest.groovy @@ -0,0 +1,7 @@ +package org.ical4j.connector.dav + +import spock.lang.Ignore + +@Ignore('container not starting correctly') +class BedeworkIntegrationTest extends AbstractIntegrationTest implements BedeworkTestSupport { +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkTestSupport.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkTestSupport.groovy new file mode 100644 index 00000000..91d44223 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/BedeworkTestSupport.groovy @@ -0,0 +1,36 @@ +package org.ical4j.connector.dav + +import org.apache.http.auth.AuthScope +import org.apache.http.auth.UsernamePasswordCredentials +import org.apache.http.client.CredentialsProvider +import org.apache.http.impl.client.BasicCredentialsProvider + +interface BedeworkTestSupport { + + default String getContainerImageName() { 'ioggstream/bedework' } + + default int getContainerPort() { 8080 } + + default List> getBindMounts() { + [] + } + + default String getRepositoryPath() { '/ucaldav' } + + default PathResolver getPathResolver() { PathResolver.Defaults.BEDEWORK } + + default String getUser() { 'test' } + + default String getWorkspace() { 'test' } + + default Map getExpectedValues() { + ['calendar-home-set': '/calendars/test'] + } + + default CredentialsProvider getCredentialsProvider() { + def credentials = new UsernamePasswordCredentials(getUser(), 'test'); + def credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, credentials); + credentialsProvider + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/CalDavLocatorFactoryTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/CalDavLocatorFactoryTest.groovy new file mode 100644 index 00000000..eaaf970f --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/CalDavLocatorFactoryTest.groovy @@ -0,0 +1,76 @@ +package org.ical4j.connector.dav + + +import spock.lang.Ignore +import spock.lang.Specification + +@Ignore('Remove dav resource from scope until other methods are stable') +class CalDavLocatorFactoryTest extends Specification { + + def 'test locator creation from href'() { + given: 'a locator factory' + CalDavLocatorFactory locatorFactory = [prefix, pathResolver] + + when: 'a locator is created' + def locator = locatorFactory.createResourceLocator(prefix, href) + + then: 'it behaves as expected' + locator.factory == locatorFactory + locator.prefix == prefix + locator.workspacePath == workspacePath + locator.resourcePath == resourcePath + locator.workspaceName == workspaceName + locator.rootLocation == isRootLocation + + where: + pathResolver | prefix | href | workspacePath | workspaceName | resourcePath | isRootLocation + PathResolver.Defaults.RADICALE | 'https://dav.example.com' | 'https://dav.example.com' | null | null | null | true + PathResolver.Defaults.RADICALE | 'https://www.example.com/dav' | 'https://www.example.com/dav' | null | null | null | true + PathResolver.Defaults.RADICALE | 'https://www.example.com/dav' | 'https://www.example.com/dav/admin' | '/admin' | 'admin' | '/admin' | false + PathResolver.Defaults.RADICALE | 'https://www.example.com/dav' | 'https://www.example.com/dav/admin/testcal' | '/admin' | 'admin' | '/admin/testcal' | false + + PathResolver.Defaults.BAIKAL | 'https://dav.example.com/dav.php' | 'https://dav.example.com/dav.php' | null | null | null | true + PathResolver.Defaults.BAIKAL | 'https://www.example.com/dav/dav.php' | 'https://www.example.com/dav/dav.php' | null | null | null | true + PathResolver.Defaults.BAIKAL | 'https://www.example.com/dav/dav.php' | 'https://www.example.com/dav/dav.php/admin' | '/admin' | 'admin' | '/admin' | false + PathResolver.Defaults.BAIKAL | 'https://www.example.com/dav/dav.php' | 'https://www.example.com/dav/dav.php/admin/testcal' | '/admin' | 'admin' | '/admin/testcal' | false + + PathResolver.Defaults.GCAL | 'https://apidata.googleusercontent.com/caldav/v2' | 'https://apidata.googleusercontent.com/caldav/v2' | null | null | null | true + PathResolver.Defaults.GCAL | 'https://apidata.googleusercontent.com/caldav/v2' | 'https://apidata.googleusercontent.com/caldav/v2/calid/user' | '/calid' | 'calid' | '/calid/user' | false + PathResolver.Defaults.GCAL | 'https://apidata.googleusercontent.com/caldav/v2' | 'https://apidata.googleusercontent.com/caldav/v2/calid/events' | '/calid' | 'calid' | '/calid/events' | false + + PathResolver.Defaults.SOGO | 'https://www.example.com/SOGo/dav' | 'https://www.example.com/SOGo/dav' | null | null | null | true + PathResolver.Defaults.SOGO | 'https://www.example.com/SOGo/dav' | 'https://www.example.com/SOGo/dav/admin' | '/admin' | 'admin' | '/admin' | false + PathResolver.Defaults.SOGO | 'https://www.example.com/SOGo/dav' | 'https://www.example.com/SOGo/dav/admin/testcal' | '/admin' | 'admin' | '/admin/testcal' | false + + } + + def 'test locator creation from workspace and resource path'() { + given: 'a locator factory' + CalDavLocatorFactory locatorFactory = [prefix, pathResolver] + + when: 'a locator is created' + def locator = locatorFactory.createResourceLocator(prefix, workspacePath, path, path == resourcePath) + + then: 'it behaves as expected' + locator.factory == locatorFactory + locator.prefix == prefix + locator.workspacePath == workspacePath + locator.resourcePath == resourcePath + locator.workspaceName == workspaceName + !locator.rootLocation + locator.getHref(false) == href + + where: + pathResolver | prefix | href | workspacePath | workspaceName | path | resourcePath + PathResolver.Defaults.RADICALE | 'https://www.example.com/dav' | 'https://www.example.com/dav/admin/testcal' | '/admin' | 'admin' | '/admin/testcal' | '/testcal' + PathResolver.Defaults.BAIKAL | 'https://www.example.com/dav/dav.php' | 'https://www.example.com/dav/dav.php/calendars/admin/testcal' | '/admin' | 'admin' | '/calendars/admin/testcal' | '/admin/testcal' + PathResolver.Defaults.GCAL | '/caldav/v2' | '/caldav/v2/calid/events' | '/calid' | 'calid' | '/calid/events' | '/calid/events' + PathResolver.Defaults.SOGO | 'https://www.example.com/SOGo/dav' | 'https://www.example.com/SOGo/dav/admin/testcal' | '/admin' | 'admin' | '/admin/testcal' | '/admin/testcal' + + PathResolver.Defaults.RADICALE | 'https://www.example.com/dav' | 'https://www.example.com/dav/admin/testcal' | '/admin' | 'admin' | '/testcal' | '/admin/testcal' + PathResolver.Defaults.BAIKAL | 'https://www.example.com/dav/dav.php' | 'https://www.example.com/dav/dav.php/admin/testcal' | '/admin' | 'admin' | '/calendars/testcal' | '/admin/testcal' + PathResolver.Defaults.GCAL | '/caldav/v2' | '/caldav/v2/calid/events' | '/calid' | 'calid' | '/calid/events' | null + PathResolver.Defaults.SOGO | 'https://www.example.com/SOGo/dav' | 'https://www.example.com/SOGo/dav/admin/testcal' | '/admin' | 'admin' | '/testcal' | '/admin/testcal' + + } +} diff --git a/src/test/groovy/net/fortuna/ical4j/connector/dav/ChandlerHubSpec.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/ChandlerHubSpec.groovy similarity index 87% rename from src/test/groovy/net/fortuna/ical4j/connector/dav/ChandlerHubSpec.groovy rename to ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/ChandlerHubSpec.groovy index ef622834..8d46fa62 100644 --- a/src/test/groovy/net/fortuna/ical4j/connector/dav/ChandlerHubSpec.groovy +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/ChandlerHubSpec.groovy @@ -29,25 +29,24 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav +package org.ical4j.connector.dav -import net.fortuna.ical4j.connector.CalendarStore; -import spock.lang.Ignore; -import spock.lang.Shared; -import spock.lang.Specification; +import spock.lang.Ignore +import spock.lang.Shared +import spock.lang.Specification class ChandlerHubSpec extends Specification { String username = '' String password = '' - @Shared CalendarStore calendarStore + @Shared def calendarStore def setupSpec() { def prodId = '-//Ben Fortuna//iCal4j Connector 1.0//EN' def url = new URL('https://hub.chandlerproject.org') - def pathResolver = PathResolver.CHANDLER; + def pathResolver = PathResolver.Defaults.CHANDLER; calendarStore = new CalDavCalendarStore(prodId, url, pathResolver) } @@ -59,7 +58,7 @@ class ChandlerHubSpec extends Specification { @Ignore def 'verify successful connection'() { setup: 'connect to chandler hub' - assert calendarStore.connect(username, password.toCharArray()) + assert calendarStore.connect(new DavSessionConfiguration().withUser(username).withPassword( password.toCharArray())) expect: 'is connected' assert calendarStore.isConnected() diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/DavClientFactoryTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/DavClientFactoryTest.groovy new file mode 100644 index 00000000..05e4cc03 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/DavClientFactoryTest.groovy @@ -0,0 +1,49 @@ +package org.ical4j.connector.dav + +import org.apache.http.auth.AuthScope +import org.apache.http.auth.UsernamePasswordCredentials +import org.apache.http.impl.client.BasicCredentialsProvider +import spock.lang.Specification + +class DavClientFactoryTest extends Specification { + + def 'test creation of dav client'() { + given: 'a client factory' + def clientFactory = new DavClientFactory().withPreemptiveAuth(true) + .withFollowRedirects(true) + + when: 'a new instance is created' + def client = clientFactory.newInstance('https://caldav.example.com') + + then: 'it is configured as expected' + client.hostConfiguration.hostName == 'caldav.example.com' + client.hostConfiguration.schemeName == 'https' + client.hostConfiguration.port == -1 + client.repositoryPath == '/' + client.clientConfiguration.followRedirects + client.clientConfiguration.preemptiveAuth + } + + def 'test creation of dav client with authentication'() { + given: 'a client factory' + def clientFactory = new DavClientFactory().withPreemptiveAuth(true) + .withFollowRedirects(true) + + and: 'a credentials provider' + def credentials = new UsernamePasswordCredentials('alice', 'secret'); + def credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(new AuthScope('caldav.example.com', 443), credentials); + clientFactory = clientFactory.withCredentialsProvider(credentialsProvider) + + when: 'a new instance is created' + def client = clientFactory.newInstance('https://caldav.example.com') + + then: 'it is configured as expected' + client.hostConfiguration.hostName == 'caldav.example.com' + client.hostConfiguration.schemeName == 'https' + client.hostConfiguration.port == -1 + client.repositoryPath == '/' + client.clientConfiguration.followRedirects + client.clientConfiguration.preemptiveAuth + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/PathResolverTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/PathResolverTest.groovy new file mode 100644 index 00000000..0dd00a38 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/PathResolverTest.groovy @@ -0,0 +1,51 @@ +package org.ical4j.connector.dav + +import spock.lang.Specification + +class PathResolverTest extends Specification { + + def 'verify principal path resolution'() { + when: 'a principal path is resolved' + def path = pathResolver.getPrincipalPath('testcal') + + then: 'the result is as expected' + path == expectedPath + + where: + pathResolver | expectedPath + PathResolver.Defaults.RADICALE | '/testcal' + PathResolver.Defaults.BAIKAL | '/principals/testcal' + PathResolver.Defaults.BEDEWORK | '/principals/users/testcal/' + PathResolver.Defaults.CALENDAR_SERVER | '/testcal/' + } + + def 'verify calendar path resolution'() { + when: 'a calendar path is resolved' + def path = pathResolver.getCalendarPath('testcal', '') + + then: 'the result is as expected' + path == expectedPath + + where: + pathResolver | expectedPath + PathResolver.Defaults.RADICALE | '/testcal' + PathResolver.Defaults.BAIKAL | '/calendars/testcal' + PathResolver.Defaults.BEDEWORK | '/ucaldav/users/testcal' + PathResolver.Defaults.CALENDAR_SERVER | '/dav/testcal' + } + + def 'verify calendar id resolution'() { + when: 'a calendar id is resolved from a path' + def id = pathResolver.getResourceName(repositoryPath, '') + + then: 'the result is as expected' + id == 'testcal' + + where: + pathResolver | repositoryPath + PathResolver.Defaults.RADICALE | '/admin/testcal' + PathResolver.Defaults.BAIKAL | '/calendars/testcal' + PathResolver.Defaults.BEDEWORK | '/users/testcal' + PathResolver.Defaults.CALENDAR_SERVER | '/testcal' + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleCalDavResourceIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleCalDavResourceIntegrationTest.groovy new file mode 100644 index 00000000..31b7d247 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleCalDavResourceIntegrationTest.groovy @@ -0,0 +1,7 @@ +package org.ical4j.connector.dav + +import spock.lang.Ignore + +@Ignore('Remove dav resource from scope until other methods are stable') +class RadicaleCalDavResourceIntegrationTest extends AbstractCalDavResourceIntegrationTest implements RadicaleTestSupport { +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleCalendarStoreIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleCalendarStoreIntegrationTest.groovy new file mode 100644 index 00000000..b6cb1e60 --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleCalendarStoreIntegrationTest.groovy @@ -0,0 +1,4 @@ +package org.ical4j.connector.dav + +class RadicaleCalendarStoreIntegrationTest extends AbstractCalendarStoreIntegrationTest implements RadicaleTestSupport { +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleDavClientIntegrationTest.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleDavClientIntegrationTest.groovy new file mode 100644 index 00000000..6881d01a --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleDavClientIntegrationTest.groovy @@ -0,0 +1,12 @@ +package org.ical4j.connector.dav + + +import org.testcontainers.utility.MountableFile + +class RadicaleDavClientIntegrationTest extends AbstractDavClientIntegrationTest implements RadicaleTestSupport { + + def setup() { + container.withCopyToContainer(MountableFile.forHostPath('src/test/resources/radicale/htpasswd',), + '/etc/radicale/users') + } +} diff --git a/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleTestSupport.groovy b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleTestSupport.groovy new file mode 100644 index 00000000..20c533dc --- /dev/null +++ b/ical4j-connector-dav/src/test/groovy/org/ical4j/connector/dav/RadicaleTestSupport.groovy @@ -0,0 +1,46 @@ +package org.ical4j.connector.dav + +import org.apache.http.auth.AuthScope +import org.apache.http.auth.UsernamePasswordCredentials +import org.apache.http.client.CredentialsProvider +import org.apache.http.impl.client.BasicCredentialsProvider +import org.testcontainers.containers.BindMode + +import static org.ical4j.connector.dav.SupportedFeature.* + +interface RadicaleTestSupport { + + default String getContainerImageName() { 'tomsquest/docker-radicale' } + + default int getContainerPort() { 5232 } + + default List> getBindMounts() { + [ + Tuple.tuple('src/test/resources/radicale', '/config', BindMode.READ_ONLY), + ] + } + + default String getRepositoryPath() { '/' } + + default String getPathPrefix() { '/' } + + default PathResolver getPathResolver() { PathResolver.Defaults.RADICALE } + + default String getUser() { 'test' } + + default String getWorkspace() { 'test' } + + default CredentialsProvider getCredentialsProvider() { + def credentials = new UsernamePasswordCredentials(getUser(), 'test'); + def credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, credentials); + credentialsProvider + } + + default Map getExpectedValues() { + [ + 'calendar-home-set': '/test', + 'supported-features': [CALENDAR_ACCESS, ADDRESSBOOK, EXTENDED_MKCOL] + ] + } +} diff --git a/src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarCollectionIntegrationTest.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarCollectionIntegrationTest.java similarity index 72% rename from src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarCollectionIntegrationTest.java rename to ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarCollectionIntegrationTest.java index 6f8231e9..d7997d02 100644 --- a/src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarCollectionIntegrationTest.java +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarCollectionIntegrationTest.java @@ -29,11 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav; +package org.ical4j.connector.dav; import junit.framework.Test; import junit.framework.TestSuite; -import net.fortuna.ical4j.connector.CalendarCollectionTest; import org.junit.Ignore; import java.net.MalformedURLException; @@ -51,20 +50,20 @@ public class CalDavCalendarCollectionIntegrationTest extends TestSuite { public static Test suite() throws MalformedURLException { - TestSuite suite = new TestSuite(CalDavCalendarCollectionIntegrationTest.class.getSimpleName()); + var suite = new TestSuite(CalDavCalendarCollectionIntegrationTest.class.getSimpleName()); - final URL url = new URL("http://mediabase.local:8088"); - String username = "fortuna"; - char[] password = "connector".toCharArray(); + final var url = new URL("http://localhost:5232"); + var username = "admin"; + var password = "admin".toCharArray(); - suite.addTest(new CalendarCollectionTest("testGetDescription", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); + suite.addTest(new CalendarCollectionTest<>("testGetDescription", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.RADICALE), username, password)); - suite.addTest(new CalendarCollectionTest("testGetDisplayName", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); + suite.addTest(new CalendarCollectionTest<>("testGetDisplayName", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.RADICALE), username, password)); - suite.addTest(new CalendarCollectionTest("testGetCalendar", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); + suite.addTest(new CalendarCollectionTest<>("testGetCalendar", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.RADICALE), username, password)); // suite.addTest(new CalendarCollectionTest("testGetMaxAttendeesPerInstance", // new CalDavCalendarStoreLifecycle(url, path), username, password)); @@ -75,17 +74,17 @@ public static Test suite() throws MalformedURLException { // suite.addTest(new CalendarCollectionTest("testGetMaxInstances", // new CalDavCalendarStoreLifecycle(url, path), username, password)); - suite.addTest(new CalendarCollectionTest("testGetMaxResourceSize", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); + suite.addTest(new CalendarCollectionTest("testGetMaxResourceSize", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.RADICALE), username, password)); // suite.addTest(new CalendarCollectionTest("testGetMinDateTime", // new CalDavCalendarStoreLifecycle(url, path), username, password)); - suite.addTest(new CalendarCollectionTest("testGetSupportedComponentTypes", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); + suite.addTest(new CalendarCollectionTest("testGetSupportedComponentTypes", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.RADICALE), username, password)); - suite.addTest(new CalendarCollectionTest("testGetCalendars", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); + suite.addTest(new CalendarCollectionTest("testGetCalendars", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.RADICALE), username, password)); return suite; } diff --git a/src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStoreIntegrationTest.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarStoreIntegrationTest.java similarity index 74% rename from src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStoreIntegrationTest.java rename to ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarStoreIntegrationTest.java index cf5dd73a..06746172 100644 --- a/src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStoreIntegrationTest.java +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarStoreIntegrationTest.java @@ -29,11 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav; +package org.ical4j.connector.dav; import junit.framework.Test; import junit.framework.TestSuite; -import net.fortuna.ical4j.connector.ObjectStoreTest; import org.junit.Ignore; import java.net.MalformedURLException; @@ -51,18 +50,18 @@ public class CalDavCalendarStoreIntegrationTest extends TestSuite { public static Test suite() throws MalformedURLException { - TestSuite suite = new TestSuite(CalDavCalendarStoreIntegrationTest.class.getSimpleName()); + var suite = new TestSuite(CalDavCalendarStoreIntegrationTest.class.getSimpleName()); - final URL url = new URL("http://mediabase.local:8088"); - String username = "fortuna"; - char[] password = "connector".toCharArray(); + final var url = new URL("http://localhost:8088"); + var username = "admin"; + var password = "admin".toCharArray(); - suite.addTest(new ObjectStoreTest("testAddCollection", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); - suite.addTest(new ObjectStoreTest("testGetCollection", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); - suite.addTest(new ObjectStoreTest("testRemoveCollection", - new CalDavCalendarStoreLifecycle(url, PathResolver.CHANDLER), username, password)); + suite.addTest(new ObjectStoreTest<>("testAddCollection", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.CHANDLER), username, password)); + suite.addTest(new ObjectStoreTest<>("testGetCollection", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.CHANDLER), username, password)); + suite.addTest(new ObjectStoreTest<>("testRemoveCollection", + new CalDavCalendarStoreLifecycle(url, PathResolver.Defaults.CHANDLER), username, password)); return suite; } diff --git a/src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStoreLifecycle.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarStoreLifecycle.java similarity index 86% rename from src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStoreLifecycle.java rename to ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarStoreLifecycle.java index 564c4594..a0751f98 100644 --- a/src/test/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStoreLifecycle.java +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalDavCalendarStoreLifecycle.java @@ -29,13 +29,12 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav; +package org.ical4j.connector.dav; -import java.net.URL; +import net.fortuna.ical4j.model.Calendar; +import org.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.CalendarStore; -import net.fortuna.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.ObjectStoreLifecycle; +import java.net.URL; /** * $Id$ @@ -45,7 +44,7 @@ * @author Ben * */ -public class CalDavCalendarStoreLifecycle implements ObjectStoreLifecycle { +public class CalDavCalendarStoreLifecycle implements ObjectStoreLifecycle { protected static final String PRODID = "-//Ben Fortuna//iCal4j Connector 1.0//EN"; @@ -53,7 +52,7 @@ public class CalDavCalendarStoreLifecycle implements ObjectStoreLifecycle store; + private ObjectStore store; public CalDavCalendarStoreLifecycle(URL url, PathResolver pathResolver) { this.url = url; @@ -61,7 +60,7 @@ public CalDavCalendarStoreLifecycle(URL url, PathResolver pathResolver) { // storePath = BASE_STORE_PATH + id + "/"; } - public ObjectStore getObjectStore() { + public ObjectStore getObjectStore() { return store; } diff --git a/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalendarCollectionTest.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalendarCollectionTest.java new file mode 100644 index 00000000..962719f4 --- /dev/null +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CalendarCollectionTest.java @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.Component; +import net.fortuna.ical4j.util.Calendars; +import org.apache.commons.io.filefilter.DirectoryFileFilter; +import org.apache.commons.io.filefilter.NotFileFilter; +import org.ical4j.connector.CalendarCollection; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStoreException; +import org.junit.Ignore; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.Arrays; +import java.util.HashSet; + +/** + * $Id$ + * + * Created on 27/02/2008 + * + * @author Ben + * + */ +@Ignore +public class CalendarCollectionTest extends ObjectCollectionTest { + + private static final String[] SUPPORTED_COMPONENTS = { Component.VAVAILABILITY, Component.VJOURNAL, + Component.VEVENT, Component.VFREEBUSY, Component.VTODO }; + + private String[] calendarUids; + + /** + * @param store + * @param username + * @param password + */ + public CalendarCollectionTest(String testMethod, ObjectStoreLifecycle lifecycle, + String username, char[] password) { + super(testMethod, lifecycle, username, password, SUPPORTED_COMPONENTS); + } + + /* + * (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception { + super.setUp(); + + // ensure collection doesn't exist prior to tests.. + try { + removeCollection(); + } catch (Exception e) { + if (!(e instanceof ObjectNotFoundException)) { + throw e; + } + } + + var uidList = new HashSet(); + + var samples = new File("etc/samples/calendars/").listFiles((FilenameFilter) new NotFileFilter( + DirectoryFileFilter.INSTANCE)); + assert samples != null; + for (var sample : samples) { + var testCal = Calendars.load(sample.getAbsolutePath()); + getCollection().merge(testCal); + + var uidCals = Calendars.split(testCal); + for (var uidCal : uidCals) { + var uid = Calendars.getUid(uidCal); + if (uid != null) { + uidList.add(uid.getValue()); + } + } + } + + calendarUids = uidList.toArray(new String[0]); + + // reconnect.. + reconnect(); + } + + /** + * Test method for + * {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxAttendeesPerInstance()}. + * + * @throws ObjectStoreException + */ + public void testGetMaxAttendeesPerInstance() throws ObjectStoreException { + // fail("Not yet implemented"); + assertEquals(Integer.valueOf(0), getCollection().getMaxAttendeesPerInstance()); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxDateTime()}. + * + * @throws ObjectStoreException + */ + public void testGetMaxDateTime() throws ObjectStoreException { + // fail("Not yet implemented"); + assertEquals(0, getCollection().getMaxDateTime()); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxInstances()}. + * + * @throws ObjectStoreException + */ + public void testGetMaxInstances() throws ObjectStoreException { + // fail("Not yet implemented"); + assertEquals(Integer.valueOf(0), getCollection().getMaxInstances()); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxResourceSize()}. + * + * @throws ObjectStoreException + */ + public void testGetMaxResourceSize() throws ObjectStoreException { + // fail("Not yet implemented"); + assertEquals(10485760, getCollection().getMaxResourceSize()); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getMinDateTime()}. + * + * @throws ObjectStoreException + */ + public void testGetMinDateTime() throws ObjectStoreException { + // fail("Not yet implemented"); + assertEquals(0, getCollection().getMinDateTime()); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getTimeZone()}. + */ + public void testGetTimeZone() { + // fail("Not yet implemented"); + } + + /** + * @throws ObjectStoreException + * + */ + public void testGetSupportedComponentTypes() throws ObjectStoreException { + assertTrue(Arrays.equals(SUPPORTED_COMPONENTS, getCollection().getSupportedComponentTypes())); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getCalendar(String)}. + * + * @throws ObjectStoreException + */ + public void testGetCalendar() throws ObjectStoreException, ObjectNotFoundException { + for (var calendarUid : calendarUids) { + var cal = getCollection().getCalendar(calendarUid); + assertNotNull("Calendar for uid: [" + calendarUid + "] not found", cal); + } + } + + /** + * @throws ObjectStoreException + */ + public void testGetCalendars() throws ObjectStoreException { + var calendars = getCollection().getAll(); + assertNotNull(calendars); + } +} diff --git a/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CardCollectionTest.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CardCollectionTest.java new file mode 100644 index 00000000..e951fa4c --- /dev/null +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/CardCollectionTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.VCardBuilder; +import net.fortuna.ical4j.vcard.VCardFileFilter; +import org.ical4j.connector.CardCollection; +import org.junit.Ignore; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; + +/** + * + * + * @author Ben + * + * Created on: 24/02/2009 + * + * $Id$ + */ +@Ignore +public class CardCollectionTest extends ObjectCollectionTest { + + private static final String[] SUPPORTED_COMPONENTS = {}; + + /** + * @param testMethod + * @param lifecycle + * @param username + * @param password + * @param supportedComponents + */ + public CardCollectionTest(String testMethod, ObjectStoreLifecycle lifecycle, String username, + char[] password) { + super(testMethod, lifecycle, username, password, SUPPORTED_COMPONENTS); + } + + /* (non-Javadoc) + * @see org.ical4j.connector.ObjectCollectionTest#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + var samples = new File("etc/samples/cards/").listFiles((FilenameFilter) VCardFileFilter.INSTANCE); + assert samples != null; + for (var sample : samples) { + var builder = new VCardBuilder(new FileInputStream(sample)); + getCollection().add(builder.build()); + } + } +} diff --git a/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectCollectionTest.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectCollectionTest.java new file mode 100644 index 00000000..f93b130a --- /dev/null +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectCollectionTest.java @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import junit.framework.TestCase; +import org.ical4j.connector.*; +import org.junit.Ignore; + +import java.io.IOException; + +/** + * + * + * @author Ben + * + * Created on: 24/02/2009 + * + * $Id$ + */ +@Ignore +public class ObjectCollectionTest> extends TestCase { + + private final ObjectStoreLifecycle lifecycle; + + private ObjectStore store; + + private final String username; + + private final char[] password; + + private C collection; + + private final String collectionId = "myCollection"; + + private final String description = "My collection of objects"; + + private final String displayName = "My Collection"; + + private final String[] supportedComponents; + + /** + * @param testMethod + * @param lifecycle + * @param username + * @param password + */ + public ObjectCollectionTest(String testMethod, ObjectStoreLifecycle lifecycle, String username, char[] password, + String[] supportedComponents) { + super(testMethod); + this.lifecycle = lifecycle; + this.username = username; + this.password = password; + this.supportedComponents = supportedComponents; + } + + /* + * (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + lifecycle.startup(); + store = lifecycle.getObjectStore(); + store.connect(username, password); + } + + /** + * @throws ObjectNotFoundException + * @throws ObjectStoreException + * + */ + protected void removeCollection() throws ObjectStoreException, ObjectNotFoundException { + getStore().getCollection(collectionId).delete(); + } + + /* + * (non-Javadoc) + * @see junit.framework.TestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + // store.removeCollection(collectionId); + store.disconnect(); + super.tearDown(); + } + + /** + * @throws ObjectStoreException + * @throws ObjectNotFoundException + * @throws FailedOperationException + * @throws IOException + */ + protected void reconnect() throws ObjectStoreException, ObjectNotFoundException, IOException, FailedOperationException { + store.disconnect(); + store = lifecycle.getObjectStore(); + store.connect(username, password); + + collection = store.getCollection(collectionId); + } + + /** + * @return the store + */ + protected final ObjectStore getStore() { + return store; + } + + /** + * @return the collection + * @throws ObjectStoreException + */ + protected final C getCollection() throws ObjectStoreException { + if (collection == null) { + try { + collection = getStore().getCollection(collectionId); + } catch (ObjectNotFoundException onfe) { + collection = getStore() + .addCollection(collectionId, displayName, description, supportedComponents, null); + // collection.setDescription(description); + // collection.setDisplayName(displayName); + } + } + return collection; + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getDescription()}. + * + * @throws ObjectStoreException + */ + public void testGetDescription() throws ObjectStoreException { + assertEquals(description, getCollection().getDescription()); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarCollection#getDisplayName()}. + * + * @throws ObjectStoreException + */ + public void testGetDisplayName() throws ObjectStoreException { + assertEquals(displayName, getCollection().getDisplayName()); + } +} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/method/CalDavMethods.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectStoreLifecycle.java similarity index 79% rename from src/main/java/net/fortuna/ical4j/connector/dav/method/CalDavMethods.java rename to ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectStoreLifecycle.java index 8a02cb77..1f1f9f44 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/method/CalDavMethods.java +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectStoreLifecycle.java @@ -29,37 +29,33 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.dav.method; +package org.ical4j.connector.dav; +import org.ical4j.connector.ObjectCollection; +import org.ical4j.connector.ObjectStore; /** * $Id$ * - * Created on 19/11/2008 + * Created on 01/03/2008 * * @author Ben * */ -public class CalDavMethods { - - /** - * - */ - public static final String METHOD_MKCALENDAR = "MKCALENDAR"; - +public interface ObjectStoreLifecycle> { + /** - * + * Initialise the calendar store. */ - public static final String METHOD_REPORT = "REPORT"; + void startup() throws Exception; /** - * + * Deconstruct the calendar store. */ - public static final String METHOD_MKTICKET = "MKTICKET"; + void shutdown() throws Exception; /** - * + * @return */ - public static final String METHOD_DELTICKET = "DELTICKET"; - + ObjectStore getObjectStore(); } diff --git a/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectStoreTest.java b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectStoreTest.java new file mode 100644 index 00000000..8bc36176 --- /dev/null +++ b/ical4j-connector-dav/src/test/java/org/ical4j/connector/dav/ObjectStoreTest.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.ical4j.connector.dav; + +import junit.framework.TestCase; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.ical4j.connector.ObjectCollection; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStore; +import org.ical4j.connector.ObjectStoreException; +import org.junit.Ignore; + +/** + * + * + * @author Ben + * + * Created on: 24/02/2009 + * + * $Id$ + */ +@Ignore +public class ObjectStoreTest> extends TestCase { + + private static final Log LOG = LogFactory.getLog(ObjectStoreTest.class); + + private final ObjectStoreLifecycle lifecycle; + + private ObjectStore store; + + private final String username; + + private final char[] password; + + private final String collectionName = "myCollection"; + + /** + * @param testMethod + * @param lifecycle + * @param username + * @param password + */ + public ObjectStoreTest(String testMethod, ObjectStoreLifecycle lifecycle, String username, char[] password) { + super(testMethod); + this.lifecycle = lifecycle; + this.username = username; + this.password = password; + } + + /* + * (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception { + super.setUp(); + + lifecycle.startup(); + store = lifecycle.getObjectStore(); + store.connect(username, password); + + LOG.info("Store connected"); + + // ensure collection doesn't exist prior to tests.. + try { + store.getCollection(collectionName).delete(); + } catch (Exception e) { + } + } + + /* + * (non-Javadoc) + * @see junit.framework.TestCase#tearDown() + */ + protected void tearDown() throws Exception { + store.disconnect(); + lifecycle.shutdown(); + super.tearDown(); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarStore#addCollection(java.lang.String)}. + */ + public void testAddCollection() throws ObjectStoreException { + var collection = store.addCollection(collectionName); + assertNotNull(collection); + } + + /** + * Test method for {@link org.ical4j.connector.jcr.RepositoryCalendarStore#getCollection(java.lang.String)}. + * + * @throws ObjectNotFoundException + */ + public void testGetCollection() throws ObjectStoreException, ObjectNotFoundException { + C collection = null; + try { + store.getCollection(collectionName); + fail("Should throw " + ObjectNotFoundException.class.getSimpleName()); + } catch (ObjectNotFoundException onfe) { + LOG.debug("Caught exception: " + onfe.getMessage()); + } + + store.addCollection(collectionName); + collection = store.getCollection(collectionName); + assertNotNull(collection); + } + + /** + * Test method for + * {@link org.ical4j.connector.jcr.RepositoryCalendarStore#removeCollection(java.lang.String)}. + * + * @throws ObjectNotFoundException + */ + public void testRemoveCollection() throws ObjectStoreException, ObjectNotFoundException { + store.addCollection(collectionName); + var collection = store.getCollection(collectionName); + assertNotNull(collection); + collection.delete(); + + try { + store.getCollection(collectionName); + fail("Should throw " + ObjectNotFoundException.class.getSimpleName()); + } catch (ObjectNotFoundException onfe) { + LOG.debug("Caught exception: " + onfe.getMessage()); + } + } +} diff --git a/ical4j-connector-dav/src/test/resources/baikal/Specific/.htaccess b/ical4j-connector-dav/src/test/resources/baikal/Specific/.htaccess new file mode 100644 index 00000000..cb24fd7f --- /dev/null +++ b/ical4j-connector-dav/src/test/resources/baikal/Specific/.htaccess @@ -0,0 +1,2 @@ +Order allow,deny +Deny from all diff --git a/ical4j-connector-dav/src/test/resources/baikal/Specific/INSTALL_DISABLED b/ical4j-connector-dav/src/test/resources/baikal/Specific/INSTALL_DISABLED new file mode 100644 index 00000000..e69de29b diff --git a/ical4j-connector-dav/src/test/resources/baikal/Specific/db/db.sqlite b/ical4j-connector-dav/src/test/resources/baikal/Specific/db/db.sqlite new file mode 100644 index 00000000..a5c21f06 Binary files /dev/null and b/ical4j-connector-dav/src/test/resources/baikal/Specific/db/db.sqlite differ diff --git a/ical4j-connector-dav/src/test/resources/baikal/baikal.yaml b/ical4j-connector-dav/src/test/resources/baikal/baikal.yaml new file mode 100644 index 00000000..fa94e2ff --- /dev/null +++ b/ical4j-connector-dav/src/test/resources/baikal/baikal.yaml @@ -0,0 +1,19 @@ +system: + configured_version: 0.9.2 + timezone: Australia/Melbourne + card_enabled: true + cal_enabled: true + dav_auth_type: Basic + admin_passwordhash: c5c8aad193b35abb041407a27e76cb563196496c5d11ec3081fdb6110db67ba2 + failed_access_message: 'user %u authentication failure for Baikal' + auth_realm: BaikalDAV + base_uri: '' + invite_from: noreply@localhost +database: + sqlite_file: /var/www/baikal/Specific/db/db.sqlite + mysql: false + mysql_host: '' + mysql_dbname: '' + mysql_username: '' + mysql_password: '' + encryption_key: df933f2bc47622b30a9e82a02ee67053 diff --git a/ical4j-connector-dav/src/test/resources/baikal/config/.htaccess b/ical4j-connector-dav/src/test/resources/baikal/config/.htaccess new file mode 100644 index 00000000..cb24fd7f --- /dev/null +++ b/ical4j-connector-dav/src/test/resources/baikal/config/.htaccess @@ -0,0 +1,2 @@ +Order allow,deny +Deny from all diff --git a/ical4j-connector-dav/src/test/resources/baikal/config/baikal.yaml b/ical4j-connector-dav/src/test/resources/baikal/config/baikal.yaml new file mode 100644 index 00000000..d82bbffd --- /dev/null +++ b/ical4j-connector-dav/src/test/resources/baikal/config/baikal.yaml @@ -0,0 +1,23 @@ +system: + configured_version: 0.10.1 + timezone: Australia/Melbourne + card_enabled: true + cal_enabled: true + dav_auth_type: Basic + admin_passwordhash: c5c8aad193b35abb041407a27e76cb563196496c5d11ec3081fdb6110db67ba2 + failed_access_message: 'user %u authentication failure for Baikal' + auth_realm: BaikalDAV + base_uri: '' + invite_from: noreply@ical4j.org +database: + sqlite_file: /var/www/baikal/Specific/db/db.sqlite + backend: sqlite + mysql_host: '' + mysql_dbname: '' + mysql_username: '' + mysql_password: '' + encryption_key: e3eb31a9e8a2527c5f96e0603d0c76fe + pgsql_host: '' + pgsql_dbname: '' + pgsql_username: '' + pgsql_password: '' diff --git a/src/test/resources/log4j.properties b/ical4j-connector-dav/src/test/resources/log4j2.properties similarity index 85% rename from src/test/resources/log4j.properties rename to ical4j-connector-dav/src/test/resources/log4j2.properties index 41baab53..b2f8a75c 100644 --- a/src/test/resources/log4j.properties +++ b/ical4j-connector-dav/src/test/resources/log4j2.properties @@ -31,10 +31,10 @@ # ## set root logging preferences.. -log4j.rootLogger=info, stdout -log4j.logger.net.fortuna.ical4j=info, stdout +rootLogger=info, stdout ## appender: stdout.. -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=[%d] %-5p [%t] %c{1} - %m%n +appender.stdout.type=Console +appender.stdout.name=stdout +appender.stdout.layout.type=PatternLayout +appender.stdout.layout.pattern=[%d] %-5p [%t] %c{1} - %m%n \ No newline at end of file diff --git a/ical4j-connector-dav/src/test/resources/radicale/config b/ical4j-connector-dav/src/test/resources/radicale/config new file mode 100644 index 00000000..2fe0c05b --- /dev/null +++ b/ical4j-connector-dav/src/test/resources/radicale/config @@ -0,0 +1,125 @@ +# -*- mode: conf -*- +# vim:ft=cfg + +# Config file for Radicale - A simple calendar server +# +# Place it into /etc/radicale/config (global) +# or ~/.config/radicale/config (user) +# +# The current values are the default ones + + +[server] + +# CalDAV server hostnames separated by a comma +# IPv4 syntax: address:port +# IPv6 syntax: [address]:port +# For example: 0.0.0.0:9999, [::]:9999 +#hosts = localhost:5232 +hosts = 0.0.0.0:5232 + +# Max parallel connections +#max_connections = 8 + +# Max size of request body (bytes) +#max_content_length = 100000000 + +# Socket timeout (seconds) +#timeout = 30 + +# SSL flag, enable HTTPS protocol +#ssl = False + +# SSL certificate path +#certificate = /etc/ssl/radicale.cert.pem + +# SSL private key +#key = /etc/ssl/radicale.key.pem + +# CA certificate for validating clients. This can be used to secure +# TCP traffic between Radicale and a reverse proxy +#certificate_authority = + + +[encoding] + +# Encoding for responding requests +#request = utf-8 + +# Encoding for storing local collections +#stock = utf-8 + + +[auth] + +# Authentication method +# Value: none | htpasswd | remote_user | http_x_remote_user +#type = none +type = htpasswd + +# Htpasswd filename +#htpasswd_filename = /etc/radicale/users +htpasswd_filename = /config/htpasswd + +# Htpasswd encryption method +# Value: plain | bcrypt | md5 +# bcrypt requires the installation of radicale[bcrypt]. +#htpasswd_encryption = md5 +htpasswd_encryption = plain + +# Incorrect authentication delay (seconds) +#delay = 1 + +# Message displayed in the client when a password is needed +#realm = Radicale - Password Required + + +[rights] + +# Rights backend +# Value: none | authenticated | owner_only | owner_write | from_file +#type = owner_only + +# File for rights management from_file +#file = /etc/radicale/rights + + +[storage] + +# Storage backend +# Value: multifilesystem | multifilesystem_nolock +#type = multifilesystem + +# Folder for storing local collections, created if not present +#filesystem_folder = /var/lib/radicale/collections +filesystem_folder = /data/collections + +# Delete sync token that are older (seconds) +#max_sync_token_age = 2592000 + +# Command that is run after changes to storage +# Example: ([ -d .git ] || git init) && git add -A && (git diff --cached --quiet || git commit -m "Changes by "%(user)s) +#hook = + + +[web] + +# Web interface backend +# Value: none | internal +#type = internal + + +[logging] + +# Threshold for the logger +# Value: debug | info | warning | error | critical +#level = warning + +# Don't include passwords in logs +#mask_passwords = True + + +[headers] + +# Additional HTTP headers +#Access-Control-Allow-Origin = * diff --git a/ical4j-connector-dav/src/test/resources/radicale/htpasswd b/ical4j-connector-dav/src/test/resources/radicale/htpasswd new file mode 100644 index 00000000..1737bd52 --- /dev/null +++ b/ical4j-connector-dav/src/test/resources/radicale/htpasswd @@ -0,0 +1,2 @@ +admin:admin +test:test diff --git a/ical4j-connector-jcr/build.gradle b/ical4j-connector-jcr/build.gradle new file mode 100644 index 00000000..187a690a --- /dev/null +++ b/ical4j-connector-jcr/build.gradle @@ -0,0 +1,8 @@ +dependencies { + api project(":ical4j-connector-api"), + 'javax.jcr:jcr:2.0', + 'org.jcrom:jcrom:2.2.0' + + testImplementation "org.apache.jackrabbit:jackrabbit-core:$jackrabbitCoreVersion", + "io.opentracing:opentracing-mock:$openTracingVersion" +} diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectCollection.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectCollection.java similarity index 97% rename from src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectCollection.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectCollection.java index 2985fad0..2a1d856e 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectCollection.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectCollection.java @@ -29,18 +29,17 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import javax.jcr.Node; -import javax.jcr.PathNotFoundException; -import javax.jcr.RepositoryException; - -import net.fortuna.ical4j.connector.ObjectCollection; +package org.ical4j.connector.jcr; +import org.ical4j.connector.ObjectCollection; import org.jcrom.AbstractJcrEntity; import org.jcrom.JcrMappingException; import org.jcrom.annotations.JcrProperty; +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.RepositoryException; + /** * @param the type of object supported by the collection * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectCollectionDao.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectCollectionDao.java similarity index 98% rename from src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectCollectionDao.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectCollectionDao.java index cd8c028d..1c8bbcda 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectCollectionDao.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectCollectionDao.java @@ -29,16 +29,15 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import java.util.List; - -import javax.jcr.Session; +package org.ical4j.connector.jcr; import org.jcrom.Jcrom; import org.jcrom.dao.AbstractJcrDAO; import org.jcrom.util.NodeFilter; +import javax.jcr.Session; +import java.util.List; + /** * @param the object type supported by the collection DAO * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectStore.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectStore.java similarity index 94% rename from src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectStore.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectStore.java index 13d9acd1..18b73742 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectStore.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/AbstractJcrObjectStore.java @@ -29,24 +29,17 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import java.util.List; - -import javax.jcr.LoginException; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.ObjectStoreException; import net.fortuna.ical4j.model.Calendar; - +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStore; +import org.ical4j.connector.ObjectStoreException; import org.jcrom.Jcrom; +import javax.jcr.*; +import java.util.List; + /** * @param the supported collection type * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendar.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendar.java similarity index 88% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendar.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendar.java index b74c6da6..487fa0af 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendar.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendar.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; @@ -54,6 +54,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * @@ -134,36 +135,36 @@ public final void setCalendar(final Calendar calendar) { // save first available summary.. if (summary == null) { - Summary summaryProp = (Summary) ((Component) component).getProperty(Property.SUMMARY); - if (summaryProp != null) { - this.summary = summaryProp.getValue(); + Optional summaryProp = ((Component) component).getProperty(Property.SUMMARY); + if (summaryProp.isPresent()) { + this.summary = summaryProp.get().getValue(); } } // save first available description.. if (description == null) { - Description descriptionProp = (Description) ((Component) component).getProperty(Property.DESCRIPTION); - if (descriptionProp != null) { + Optional descriptionProp = ((Component) component).getProperty(Property.DESCRIPTION); + if (descriptionProp.isPresent()) { description = new JcrFile(); description.setName("text"); description.setMimeType("text/plain"); - description.setDataProvider(new JcrDataProviderImpl(descriptionProp.getValue().getBytes())); + description.setDataProvider(new JcrDataProviderImpl(descriptionProp.get().getValue().getBytes())); description.setLastModified(java.util.Calendar.getInstance()); } } // save attachments.. attachments.clear(); - PropertyList attachments = ((Component) component).getProperties(Property.ATTACH); - for (Object attach : attachments) { + List attachments = ((Component) component).getProperties(Property.ATTACH); + for (Attach attach : attachments) { try { JcrFile attachment = new JcrFile(); attachment.setName("attachment"); - if (Value.BINARY.equals(((Property) attach).getParameter(Parameter.VALUE))) { + if (Value.BINARY.equals(attach.getParameter(Parameter.VALUE))) { attachment.setDataProvider(new JcrDataProviderImpl(((Attach) attach).getBinary())); - FmtType contentType = (FmtType) ((Property) attach).getParameter(Parameter.FMTTYPE); - if (contentType != null) { - attachment.setMimeType(contentType.getValue()); + Optional contentType = attach.getParameter(Parameter.FMTTYPE); + if (contentType.isPresent()) { + attachment.setMimeType(contentType.get().getValue()); } } else { diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollection.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarCollection.java similarity index 92% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollection.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarCollection.java index caaa449a..6ea9a1a4 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollection.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarCollection.java @@ -29,23 +29,21 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import net.fortuna.ical4j.connector.CalendarCollection; -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.enums.MediaType; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.model.ConstraintViolationException; +import net.fortuna.ical4j.model.Property; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.util.Calendars; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.ical4j.connector.*; import org.jcrom.annotations.JcrProperty; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; +import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -119,12 +117,19 @@ private JcrCalendarCollectionDao getCollectionDao() { } return collectionDao; } - + + @Override + public List listObjectUids() { + //TODO: extract UIDs from calendar DAO.. + return null; + } + /** * {@inheritDoc} */ - public void addCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { + public Uid addCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { addCalendar(calendar, true); + return calendar.getRequiredProperty(Property.UID); } private void addCalendar(Calendar calendar, boolean saveChanges) @@ -233,7 +238,7 @@ public Calendar getCalendar(String uid) throws ObjectNotFoundException { /** * {@inheritDoc} */ - public Calendar[] getComponents() throws ObjectStoreException { + public Iterable getComponents() throws ObjectStoreException { List retVal = new ArrayList(); // for (Object jcrCal : calendars.values()) { // NodeIterator childNodes; @@ -254,7 +259,7 @@ public Calendar[] getComponents() throws ObjectStoreException { // catch (RepositoryException e1) { // throw new ObjectStoreException("Unexpected error", e1); // } - return retVal.toArray(new Calendar[retVal.size()]); + return retVal; } /** @@ -267,9 +272,9 @@ public Integer getMaxAttendeesPerInstance() { /** * {@inheritDoc} */ - public String getMaxDateTime() { + public Instant getMaxDateTime() { if (maxDateTime != null) { - return maxDateTime.toString(); + return maxDateTime.toInstant(); } return null; } @@ -291,9 +296,9 @@ public long getMaxResourceSize() { /** * {@inheritDoc} */ - public String getMinDateTime() { + public Instant getMinDateTime() { if (minDateTime != null) { - return minDateTime.toString(); + return minDateTime.toInstant(); } return null; } @@ -340,17 +345,20 @@ public Calendar removeCalendar(String uid) throws ObjectStoreException, ObjectNo /** * {@inheritDoc} */ - public void merge(Calendar calendar) throws FailedOperationException, ObjectStoreException { + public Uid[] merge(Calendar calendar) throws FailedOperationException, ObjectStoreException { + List uids = new ArrayList<>(); try { Calendar[] uidCalendars = Calendars.split(calendar); for (int i = 0; i < uidCalendars.length; i++) { addCalendar(uidCalendars[i], false); + uids.add(uidCalendars[i].getRequiredProperty(Property.UID)); } saveChanges(); } catch (ConstraintViolationException cve) { throw new FailedOperationException("Invalid calendar format", cve); } + return uids.toArray(new Uid[0]); } /** diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollectionDao.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarCollectionDao.java similarity index 97% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollectionDao.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarCollectionDao.java index 4cd30fef..f4bd31f3 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollectionDao.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarCollectionDao.java @@ -29,12 +29,12 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import javax.jcr.Session; +package org.ical4j.connector.jcr; import org.jcrom.Jcrom; +import javax.jcr.Session; + /** * * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarDao.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarDao.java similarity index 98% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarDao.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarDao.java index c0d3610e..e9fb5ea4 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarDao.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarDao.java @@ -29,16 +29,15 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import java.util.List; - -import javax.jcr.Session; +package org.ical4j.connector.jcr; import org.jcrom.Jcrom; import org.jcrom.dao.AbstractJcrDAO; import org.jcrom.util.NodeFilter; +import javax.jcr.Session; +import java.util.List; + /** * * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStore.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarStore.java similarity index 96% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStore.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarStore.java index afd87766..c67a46f7 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStore.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCalendarStore.java @@ -29,14 +29,13 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import javax.jcr.Repository; - -import net.fortuna.ical4j.connector.CalendarStore; +package org.ical4j.connector.jcr; +import org.ical4j.connector.CalendarStore; import org.jcrom.Jcrom; +import javax.jcr.Repository; + /** * $Id$ * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCard.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCard.java similarity index 90% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCard.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCard.java index fe9b6147..fe345bbd 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCard.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCard.java @@ -29,23 +29,23 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import java.io.IOException; - -import net.fortuna.ical4j.connector.dav.enums.MediaType; import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.vcard.Property.Id; +import net.fortuna.ical4j.vcard.PropertyName; import net.fortuna.ical4j.vcard.VCard; import net.fortuna.ical4j.vcard.VCardBuilder; import net.fortuna.ical4j.vcard.property.Uid; - +import org.ical4j.connector.MediaType; import org.jcrom.AbstractJcrEntity; import org.jcrom.JcrDataProviderImpl; import org.jcrom.JcrFile; import org.jcrom.annotations.JcrFileNode; import org.jcrom.annotations.JcrProperty; +import java.io.IOException; +import java.util.Optional; + /** * * @@ -87,10 +87,10 @@ public VCard getCard() throws IOException, ParserException { public void setCard(VCard card) { this.card = card; - Uid uidProp = (Uid) card.getProperty(Id.UID); - if (uidProp != null) { - setName(uidProp.getValue()); - this.uid = uidProp.getValue(); + Optional uidProp = card.getProperty(PropertyName.UID.toString()); + if (uidProp.isPresent()) { + setName(uidProp.get().getValue()); + this.uid = uidProp.get().getValue(); } else { setName("card"); diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardCollection.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardCollection.java similarity index 66% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardCollection.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardCollection.java index 421c07be..7c92b2b8 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardCollection.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardCollection.java @@ -29,22 +29,23 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import net.fortuna.ical4j.connector.CardCollection; -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; import net.fortuna.ical4j.model.ConstraintViolationException; -import net.fortuna.ical4j.vcard.Property.Id; +import net.fortuna.ical4j.vcard.PropertyName; import net.fortuna.ical4j.vcard.VCard; import net.fortuna.ical4j.vcard.property.Uid; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.ical4j.connector.CardCollection; +import org.ical4j.connector.FailedOperationException; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStoreException; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -76,43 +77,65 @@ public JcrCardCollection() { // cards = new ArrayList(); } + @Override + public List listObjectUids() { + //TODO: extract UIDs from vcard DAO.. + return null; + } + /** * {@inheritDoc} */ - public void addCard(VCard card) throws ObjectStoreException, ConstraintViolationException { - + public Uid addCard(VCard card) throws ObjectStoreException, ConstraintViolationException { + Uid uid = card.getRequiredProperty(PropertyName.UID.toString()); + save(card); + return uid; + } + + @Override + public Uid[] merge(VCard card) throws ObjectStoreException, ConstraintViolationException { + Uid uid = card.getRequiredProperty(PropertyName.UID.toString()); + VCard existing = null; + try { + existing = getCard(uid.getValue()); + } catch (ObjectNotFoundException | FailedOperationException e) { + throw new ObjectStoreException(e); + } + existing.addAll(card.getProperties()); + save(card); + return Collections.singletonList(uid).toArray(new Uid[0]); + } + + private void save(VCard card) throws ObjectStoreException { + // initialise cards node.. try { try { getNode().getNode("cards"); - } - catch (PathNotFoundException e) { + } catch (PathNotFoundException e) { getNode().addNode("cards"); } - } - catch (RepositoryException e) { + } catch (RepositoryException e) { throw new ObjectStoreException("Unexpected error", e); } - + JcrCard jcrCard = null; boolean update = false; - - Uid uid = (Uid) card.getProperty(Id.UID); - if (uid != null) { - List jcrCards = getCardDao().findByUid( - getStore().getJcrom().getPath(this) + "/cards", uid.getValue()); - if (!jcrCards.isEmpty()) { - jcrCard = jcrCards.get(0); - update = true; - } + + Uid uid = card.getRequiredProperty(PropertyName.UID.toString()); + List jcrCards = getCardDao().findByUid(getStore().getJcrom().getPath(this) + "/cards", + uid.getValue()); + if (!jcrCards.isEmpty()) { + jcrCard = jcrCards.get(0); + update = true; } - + if (jcrCard == null) { jcrCard = new JcrCard(); } - + jcrCard.setCard(card); - + if (update) { getCardDao().update(jcrCard); } @@ -126,10 +149,15 @@ public VCard removeCard(String uid) throws ObjectNotFoundException, FailedOperat return null; } + @Override + public VCard getCard(String uid) throws ObjectNotFoundException, FailedOperationException { + return null; + } + /** * {@inheritDoc} */ - public VCard[] getComponents() throws ObjectStoreException { + public Iterable getComponents() throws ObjectStoreException { List cards = new ArrayList(); List jcrCards = getCardDao().findAll(getStore().getJcrom().getPath(this) + "/cards"); for (JcrCard card : jcrCards) { @@ -140,9 +168,14 @@ public VCard[] getComponents() throws ObjectStoreException { LOG.error("Unexcepted error", e); } } - return cards.toArray(new VCard[cards.size()]); + return cards; } - + + @Override + public VCard[] export() throws ObjectStoreException { + return new VCard[0]; + } + /** * @return */ diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardCollectionDao.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardCollectionDao.java similarity index 97% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardCollectionDao.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardCollectionDao.java index fb67a645..76221917 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardCollectionDao.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardCollectionDao.java @@ -29,12 +29,12 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import javax.jcr.Session; +package org.ical4j.connector.jcr; import org.jcrom.Jcrom; +import javax.jcr.Session; + /** * * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardDao.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardDao.java similarity index 98% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardDao.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardDao.java index ce8c7719..560d49b3 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardDao.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardDao.java @@ -29,16 +29,15 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import java.util.List; - -import javax.jcr.Session; +package org.ical4j.connector.jcr; import org.jcrom.Jcrom; import org.jcrom.dao.AbstractJcrDAO; import org.jcrom.util.NodeFilter; +import javax.jcr.Session; +import java.util.List; + /** * * diff --git a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardStore.java b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardStore.java similarity index 96% rename from src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardStore.java rename to ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardStore.java index 3c9d760e..8c16b772 100644 --- a/src/main/java/net/fortuna/ical4j/connector/jcr/JcrCardStore.java +++ b/ical4j-connector-jcr/src/main/java/org/ical4j/connector/jcr/JcrCardStore.java @@ -29,14 +29,13 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; - -import javax.jcr.Repository; - -import net.fortuna.ical4j.connector.CardStore; +package org.ical4j.connector.jcr; +import org.ical4j.connector.CardStore; import org.jcrom.Jcrom; +import javax.jcr.Repository; + /** * * diff --git a/src/test/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectStoreLifecycle.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/AbstractJcrObjectStoreLifecycle.java similarity index 93% rename from src/test/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectStoreLifecycle.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/AbstractJcrObjectStoreLifecycle.java index 3d5ffd33..224a2a40 100644 --- a/src/test/java/net/fortuna/ical4j/connector/jcr/AbstractJcrObjectStoreLifecycle.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/AbstractJcrObjectStoreLifecycle.java @@ -29,20 +29,17 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import java.io.File; -import java.util.Hashtable; +import org.apache.commons.io.FileUtils; +import org.apache.jackrabbit.core.jndi.RegistryHelper; +import org.jcrom.Jcrom; import javax.jcr.Repository; import javax.naming.Context; import javax.naming.InitialContext; - -import net.fortuna.ical4j.connector.ObjectStoreLifecycle; - -import org.apache.commons.io.FileUtils; -import org.apache.jackrabbit.core.jndi.RegistryHelper; -import org.jcrom.Jcrom; +import java.io.File; +import java.util.Hashtable; /** * @@ -79,7 +76,7 @@ public AbstractJcrObjectStoreLifecycle(String testDir, String name) { /* * (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectStoreLifecycle#shutdown() + * @see org.ical4j.connector.ObjectStoreLifecycle#shutdown() */ public void shutdown() throws Exception { RegistryHelper.unregisterRepository(context, repoName); @@ -87,7 +84,7 @@ public void shutdown() throws Exception { /* * (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectStoreLifecycle#startup() + * @see org.ical4j.connector.ObjectStoreLifecycle#startup() */ @SuppressWarnings("unchecked") public void startup() throws Exception { diff --git a/src/test/java/net/fortuna/ical4j/connector/CalendarCollectionTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/CalendarCollectionTest.java similarity index 87% rename from src/test/java/net/fortuna/ical4j/connector/CalendarCollectionTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/CalendarCollectionTest.java index e9522352..149f932f 100644 --- a/src/test/java/net/fortuna/ical4j/connector/CalendarCollectionTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/CalendarCollectionTest.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector.jcr; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.model.Component; @@ -37,6 +37,9 @@ import net.fortuna.ical4j.util.Calendars; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.NotFileFilter; +import org.ical4j.connector.CalendarCollection; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStoreException; import org.junit.Ignore; import java.io.File; @@ -112,7 +115,7 @@ protected void setUp() throws Exception { /** * Test method for - * {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxAttendeesPerInstance()}. + * {@link JcrCalendarCollection#getMaxAttendeesPerInstance()}. * * @throws ObjectStoreException */ @@ -122,7 +125,7 @@ public void testGetMaxAttendeesPerInstance() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxDateTime()}. + * Test method for {@link JcrCalendarCollection#getMaxDateTime()}. * * @throws ObjectStoreException */ @@ -132,7 +135,7 @@ public void testGetMaxDateTime() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxInstances()}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarCollection#getMaxInstances()}. * * @throws ObjectStoreException */ @@ -142,7 +145,7 @@ public void testGetMaxInstances() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getMaxResourceSize()}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarCollection#getMaxResourceSize()}. * * @throws ObjectStoreException */ @@ -152,7 +155,7 @@ public void testGetMaxResourceSize() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getMinDateTime()}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarCollection#getMinDateTime()}. * * @throws ObjectStoreException */ @@ -162,7 +165,7 @@ public void testGetMinDateTime() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getTimeZone()}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarCollection#getTimeZone()}. */ public void testGetTimeZone() { // fail("Not yet implemented"); @@ -177,7 +180,7 @@ public void testGetSupportedComponentTypes() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getCalendar(String)}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarCollection#getCalendar(String)}. * * @throws ObjectStoreException */ @@ -192,7 +195,7 @@ public void testGetCalendar() throws ObjectStoreException, ObjectNotFoundExcepti * @throws ObjectStoreException */ public void testGetCalendars() throws ObjectStoreException { - Calendar[] calendars = getCollection().getComponents(); + Iterable calendars = getCollection().getComponents(); assertNotNull(calendars); } } diff --git a/src/test/java/net/fortuna/ical4j/connector/CardCollectionTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/CardCollectionTest.java similarity index 93% rename from src/test/java/net/fortuna/ical4j/connector/CardCollectionTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/CardCollectionTest.java index 9507a232..7b9ddeb3 100644 --- a/src/test/java/net/fortuna/ical4j/connector/CardCollectionTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/CardCollectionTest.java @@ -29,16 +29,17 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FilenameFilter; +package org.ical4j.connector.jcr; import net.fortuna.ical4j.vcard.VCardBuilder; import net.fortuna.ical4j.vcard.VCardFileFilter; +import org.ical4j.connector.CardCollection; import org.junit.Ignore; +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; + /** * * @@ -62,12 +63,12 @@ public class CardCollectionTest extends * @param supportedComponents */ public CardCollectionTest(String testMethod, ObjectStoreLifecycle lifecycle, String username, - char[] password) { + char[] password) { super(testMethod, lifecycle, username, password, SUPPORTED_COMPONENTS); } /* (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectCollectionTest#setUp() + * @see org.ical4j.connector.ObjectCollectionTest#setUp() */ @Override protected void setUp() throws Exception { diff --git a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollectionIntegrationTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarCollectionIntegrationTest.java similarity index 97% rename from src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollectionIntegrationTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarCollectionIntegrationTest.java index 3c1eaa3e..d325523c 100644 --- a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarCollectionIntegrationTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarCollectionIntegrationTest.java @@ -29,11 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; import junit.framework.Test; import junit.framework.TestSuite; -import net.fortuna.ical4j.connector.CalendarCollectionTest; /** * $Id$ diff --git a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStoreIntegrationTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarStoreIntegrationTest.java similarity index 96% rename from src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStoreIntegrationTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarStoreIntegrationTest.java index c11e8adc..ea2744b9 100644 --- a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStoreIntegrationTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarStoreIntegrationTest.java @@ -29,11 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; import junit.framework.Test; import junit.framework.TestSuite; -import net.fortuna.ical4j.connector.ObjectStoreTest; /** * $Id$ diff --git a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStoreLifecycle.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarStoreLifecycle.java similarity index 92% rename from src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStoreLifecycle.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarStoreLifecycle.java index 05470934..23ed9115 100644 --- a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCalendarStoreLifecycle.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCalendarStoreLifecycle.java @@ -29,11 +29,11 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import java.io.File; +import org.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.ObjectStore; +import java.io.File; /** * $Id$ @@ -56,7 +56,7 @@ public JcrCalendarStoreLifecycle(String name) { /* * (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectStoreLifecycle#getCalendarStore() + * @see org.ical4j.connector.ObjectStoreLifecycle#getCalendarStore() */ public ObjectStore getObjectStore() { return new JcrCalendarStore(getJcrom(), getRepository(), "/store"); diff --git a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardCollectionIntegrationTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardCollectionIntegrationTest.java similarity index 96% rename from src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardCollectionIntegrationTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardCollectionIntegrationTest.java index 8c2bd7bd..5db38321 100644 --- a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardCollectionIntegrationTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardCollectionIntegrationTest.java @@ -29,9 +29,8 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import net.fortuna.ical4j.connector.CardCollectionTest; import junit.framework.Test; import junit.framework.TestSuite; diff --git a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardStoreIntegrationTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardStoreIntegrationTest.java similarity index 96% rename from src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardStoreIntegrationTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardStoreIntegrationTest.java index a5eebb0e..40a01068 100644 --- a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardStoreIntegrationTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardStoreIntegrationTest.java @@ -29,11 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; import junit.framework.Test; import junit.framework.TestSuite; -import net.fortuna.ical4j.connector.ObjectStoreTest; /** * diff --git a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardStoreLifecycle.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardStoreLifecycle.java similarity index 92% rename from src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardStoreLifecycle.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardStoreLifecycle.java index 420eb761..a86cda6a 100644 --- a/src/test/java/net/fortuna/ical4j/connector/jcr/JcrCardStoreLifecycle.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/JcrCardStoreLifecycle.java @@ -29,11 +29,11 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector.jcr; +package org.ical4j.connector.jcr; -import java.io.File; +import org.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.ObjectStore; +import java.io.File; /** * @@ -58,7 +58,7 @@ public JcrCardStoreLifecycle(String name) { } /* (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectStoreLifecycle#getObjectStore() + * @see org.ical4j.connector.ObjectStoreLifecycle#getObjectStore() */ public ObjectStore getObjectStore() { return new JcrCardStore(getJcrom(), getRepository(), "/contacts"); diff --git a/src/test/java/net/fortuna/ical4j/connector/ObjectCollectionTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectCollectionTest.java similarity index 95% rename from src/test/java/net/fortuna/ical4j/connector/ObjectCollectionTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectCollectionTest.java index 48275f5e..6ca90c2d 100644 --- a/src/test/java/net/fortuna/ical4j/connector/ObjectCollectionTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectCollectionTest.java @@ -29,13 +29,14 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; - -import java.io.IOException; +package org.ical4j.connector.jcr; import junit.framework.TestCase; +import org.ical4j.connector.*; import org.junit.Ignore; +import java.io.IOException; + /** * * @@ -152,7 +153,7 @@ protected final T getCollection() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getDescription()}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarCollection#getDescription()}. * * @throws ObjectStoreException */ @@ -161,7 +162,7 @@ public void testGetDescription() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarCollection#getDisplayName()}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarCollection#getDisplayName()}. * * @throws ObjectStoreException */ diff --git a/src/test/java/net/fortuna/ical4j/connector/ObjectStoreLifecycle.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectStoreLifecycle.java similarity index 94% rename from src/test/java/net/fortuna/ical4j/connector/ObjectStoreLifecycle.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectStoreLifecycle.java index 09a7653c..d3549604 100644 --- a/src/test/java/net/fortuna/ical4j/connector/ObjectStoreLifecycle.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectStoreLifecycle.java @@ -29,7 +29,10 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector.jcr; + +import org.ical4j.connector.ObjectCollection; +import org.ical4j.connector.ObjectStore; /** * $Id$ diff --git a/src/test/java/net/fortuna/ical4j/connector/ObjectStoreTest.java b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectStoreTest.java similarity index 90% rename from src/test/java/net/fortuna/ical4j/connector/ObjectStoreTest.java rename to ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectStoreTest.java index 84e9379d..0d71371d 100644 --- a/src/test/java/net/fortuna/ical4j/connector/ObjectStoreTest.java +++ b/ical4j-connector-jcr/src/test/java/org/ical4j/connector/jcr/ObjectStoreTest.java @@ -29,12 +29,15 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.fortuna.ical4j.connector; +package org.ical4j.connector.jcr; +import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - -import junit.framework.TestCase; +import org.ical4j.connector.ObjectCollection; +import org.ical4j.connector.ObjectNotFoundException; +import org.ical4j.connector.ObjectStore; +import org.ical4j.connector.ObjectStoreException; import org.junit.Ignore; /** @@ -105,7 +108,7 @@ protected void tearDown() throws Exception { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarStore#addCollection(java.lang.String)}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarStore#addCollection(String)}. */ public void testAddCollection() throws ObjectStoreException { T collection = store.addCollection(collectionName); @@ -113,7 +116,7 @@ public void testAddCollection() throws ObjectStoreException { } /** - * Test method for {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarStore#getCollection(java.lang.String)}. + * Test method for {@link org.ical4j.connector.jcr.JcrCalendarStore#getCollection(String)}. * * @throws ObjectNotFoundException */ @@ -133,7 +136,7 @@ public void testGetCollection() throws ObjectStoreException, ObjectNotFoundExcep /** * Test method for - * {@link net.fortuna.ical4j.connector.jcr.RepositoryCalendarStore#removeCollection(java.lang.String)}. + * {@link org.ical4j.connector.jcr.JcrCalendarStore#removeCollection(String)}. * * @throws ObjectNotFoundException */ diff --git a/src/test/resources/jndi.properties b/ical4j-connector-jcr/src/test/resources/jndi.properties similarity index 100% rename from src/test/resources/jndi.properties rename to ical4j-connector-jcr/src/test/resources/jndi.properties diff --git a/src/test/resources/repository.xml b/ical4j-connector-jcr/src/test/resources/repository.xml similarity index 100% rename from src/test/resources/repository.xml rename to ical4j-connector-jcr/src/test/resources/repository.xml diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..b5b6b6e4 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,6 @@ +rootProject.name = 'ical4j-connector' + +include 'ical4j-connector-api' +include 'ical4j-connector-dav' +//include 'ical4j-connector-jcr' +//include 'ical4j-connector-jpa' diff --git a/src/main/assembly/bin.xml b/src/main/assembly/bin.xml deleted file mode 100644 index ea1eb094..00000000 --- a/src/main/assembly/bin.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - tar.gz - tar.bz2 - zip - - - - - README - LICENSE - CHANGELOG - - - - etc - etc - - rfc4791.txt - draft-ietf-vcarddav-carddav-03.txt - - - - target - lib - - ${project.finalName}.jar - - - - target/site/apidocs - docs/apidocs - - - - - lib - false - runtime - - - diff --git a/src/main/assembly/src.xml b/src/main/assembly/src.xml deleted file mode 100644 index 725a8478..00000000 --- a/src/main/assembly/src.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - src - - tar.gz - tar.bz2 - zip - - - - - README - LICENSE - CHANGELOG - pom.xml - - - - src/main - src/main - - assembly/** - - - - src/test - src/test - - - etc - etc - - rfc4791.txt - draft-ietf-vcarddav-carddav-03.txt - checkstyle.xml - - - - target - lib - - ${project.finalName}.jar - - - - target/site/apidocs - docs/apidocs - - - - - lib - false - test - - - diff --git a/src/main/java/net/fortuna/ical4j/connector/MediaType.java b/src/main/java/net/fortuna/ical4j/connector/MediaType.java deleted file mode 100644 index bc52ec97..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/MediaType.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector; - -/** - * $Id$ - * - * Created on 20/02/2008 - * - * Possible media types support by a {@link CalendarCollection}. - * @author Ben - * - */ -public enum MediaType { - - /** - * iCalendar media type. - */ - ICALENDAR_2_0("text/calendar", "2.0"), - - /** - * vCard media type. - */ - VCARD_4_0("text/vcard", "4.0"); - - private String contentType; - - private String version; - - /** - * @param contentType - * @param version - */ - private MediaType(String contentType, String version) { - this.contentType = contentType; - this.version = version; - } - - /** - * @return the contentType - */ - public String getContentType() { - return contentType; - } - - /** - * @return the version - */ - public String getVersion() { - return version; - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/ObjectCollection.java b/src/main/java/net/fortuna/ical4j/connector/ObjectCollection.java deleted file mode 100644 index 67784f0e..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/ObjectCollection.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector; - - - -/** - * @param the object type stored by the collection - * - * $Id$ - * - * Created on 27/09/2008 - * - * @author Ben - * - */ -public interface ObjectCollection { - - /** - * @return the collection name - */ - String getDisplayName(); - - /** - * @return the collection description - */ - String getDescription(); - - /** - * Returns all objects stored in the collection. - * @return an array of collection objects - * @throws ObjectStoreException where an unexpected error occurs - */ - T[] getComponents() throws ObjectStoreException; - - /** - * Returns a property value for the collection. - * @param the property return type - * @param name the property name - * @param type the property return type - * @return a value of the specified type, or null if no property is found - */ -// T getProperty(String propertyName, Class type); - -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/CalDavCalendarCollection.java b/src/main/java/net/fortuna/ical4j/connector/dav/CalDavCalendarCollection.java deleted file mode 100644 index 6f7b4c74..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/CalDavCalendarCollection.java +++ /dev/null @@ -1,710 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -import net.fortuna.ical4j.connector.CalendarCollection; -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.method.GetMethod; -import net.fortuna.ical4j.connector.dav.method.MkCalendarMethod; -import net.fortuna.ical4j.connector.dav.method.PutMethod; -import net.fortuna.ical4j.connector.dav.method.ReportMethod; -import net.fortuna.ical4j.connector.dav.property.BaseDavPropertyName; -import net.fortuna.ical4j.connector.dav.property.CSDavPropertyName; -import net.fortuna.ical4j.connector.dav.property.CalDavPropertyName; -import net.fortuna.ical4j.connector.dav.property.ICalPropertyName; -import net.fortuna.ical4j.data.CalendarBuilder; -import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.model.Calendar; -import net.fortuna.ical4j.model.Component; -import net.fortuna.ical4j.model.ConstraintViolationException; -import net.fortuna.ical4j.model.DateTime; -import net.fortuna.ical4j.model.property.Uid; -import net.fortuna.ical4j.util.Calendars; -import org.apache.http.HttpResponse; -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.*; -import org.apache.jackrabbit.webdav.client.methods.HttpDelete; -import org.apache.jackrabbit.webdav.client.methods.XmlEntity; -import org.apache.jackrabbit.webdav.property.*; -import org.apache.jackrabbit.webdav.security.SecurityConstants; -import org.apache.jackrabbit.webdav.version.report.ReportInfo; -import org.apache.jackrabbit.webdav.version.report.ReportType; -import org.apache.jackrabbit.webdav.xml.DomUtil; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.StringReader; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -/** - * $Id$ - * - * Created on 24/02/2008 - * - * @author Ben - * - */ -public class CalDavCalendarCollection extends AbstractDavObjectCollection implements CalendarCollection { - - /** - * Only {@link CalDavCalendarStore} should be calling this, so default modifier is applied. - * - * @param calDavCalendarStore - * @param id - */ - CalDavCalendarCollection(CalDavCalendarStore calDavCalendarStore, String id) { - this(calDavCalendarStore, id, null, null); - } - - /** - * Only {@link CalDavCalendarStore} should be calling this, so default modifier is applied. - * - * @param calDavCalendarStore - * @param id - * @param displayName - * @param description - */ - CalDavCalendarCollection(CalDavCalendarStore calDavCalendarStore, String id, String displayName, String description) { - - super(calDavCalendarStore, id); - properties.add(new DefaultDavProperty<>(DavPropertyName.DISPLAYNAME, displayName)); - properties.add(new DefaultDavProperty<>(CalDavPropertyName.CALENDAR_DESCRIPTION, description)); - } - - CalDavCalendarCollection(CalDavCalendarStore calDavCalendarStore, String id, DavPropertySet _properties) { - this(calDavCalendarStore, id, null, null); - this.properties = _properties; - } - - /** - * Creates this collection on the CalDAV server. - * - * @throws IOException - * @throws ObjectStoreException - */ - final void create() throws IOException, ObjectStoreException { - MkCalendarMethod mkCalendarMethod = new MkCalendarMethod(getPath()); - - MkCalendar mkcalendar = new MkCalendar(); - mkcalendar.setProperties(properties); - System.out.println("properties: " + properties.getContentSize()); - mkCalendarMethod.setEntity(XmlEntity.create(mkcalendar)); - - HttpResponse httpResponse = getStore().getClient().execute(mkCalendarMethod); - if (!mkCalendarMethod.succeeded(httpResponse)) { - throw new ObjectStoreException(httpResponse.getStatusLine().getStatusCode() + ": " + httpResponse.getStatusLine().getReasonPhrase()); - } - } - - /** - * @return an array of calendar objects - * @deprecated Use the getEvents() method - * @see CalendarCollection#getComponents() - */ - @Deprecated - public Calendar[] getCalendars() { - return getComponentsByType(Component.VEVENT); - } - - /** - * @return and array of calendar objects - */ - public Calendar[] getEvents() { - return getComponentsByType(Component.VEVENT); - } - - /** - * @return and array of calendar objects - */ - public Calendar[] getTasks() { - return getComponentsByType(Component.VTODO); - } - - /** - * @param componentType - * the type of component - * @return and array of calendar objects - */ - public Calendar[] getComponentsByType(String componentType) { - try { - DavPropertyNameSet properties = new DavPropertyNameSet(); - properties.add(DavPropertyName.GETETAG); - properties.add(CalDavPropertyName.CALENDAR_DATA); - - Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - Element filter = DomUtil.createElement(document, CalDavConstants.PROPERTY_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - Element calFilter = DomUtil.createElement(document, CalDavConstants.PROPERTY_COMP_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - calFilter.setAttribute(CalDavConstants.ATTRIBUTE_NAME, Calendar.VCALENDAR); - Element eventFilter = DomUtil.createElement(document, CalDavConstants.PROPERTY_COMP_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - eventFilter.setAttribute(CalDavConstants.ATTRIBUTE_NAME, componentType); - calFilter.appendChild(eventFilter); - filter.appendChild(calFilter); - - ReportInfo info = new ReportInfo(ReportMethod.CALENDAR_QUERY, 1, properties); - info.setContentElement(filter); - - ReportMethod method = new ReportMethod(getPath(), info); - HttpResponse httpResponse = getStore().getClient().execute(method); - if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_MULTI_STATUS) { - return method.getCalendars(httpResponse); - } else { - return new Calendar[0]; - } - } catch (DavException | IOException | ParserConfigurationException | ParserException e) { - throw new RuntimeException(e); - } - } - - /** - * Provides a human-readable description of the calendar collection. - */ - public String getDescription() { - try { - return getProperty(CalDavPropertyName.CALENDAR_DESCRIPTION, String.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Human-readable name of the collection. - */ - public String getDisplayName() { - try { - return getProperty(DavPropertyName.DISPLAYNAME, String.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Provides a numeric value indicating the maximum number of ATTENDEE properties in any instance of a calendar - * object resource stored in a calendar collection. - */ - public Integer getMaxAttendeesPerInstance() { - try { - return getProperty(CalDavPropertyName.MAX_ATTENDEES_PER_INSTANCE, Integer.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Provides a DATE-TIME value indicating the latest date and time (in UTC) that the server is willing to accept for - * any DATE or DATE-TIME value in a calendar object resource stored in a calendar collection. - */ - public String getMaxDateTime() { - try { - return getProperty(CalDavPropertyName.MAX_DATE_TIME, String.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Provides a numeric value indicating the maximum number of recurrence instances that a calendar object resource - * stored in a calendar collection can generate. - */ - public Integer getMaxInstances() { - try { - return getProperty(CalDavPropertyName.MAX_INSTANCES, Integer.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Provides a numeric value indicating the maximum size of a resource in octets that the server is willing to accept - * when a calendar object resource is stored in a calendar collection. 0 = no limits. - */ - public long getMaxResourceSize() { - try { - Long size = getProperty(CalDavPropertyName.MAX_RESOURCE_SIZE, Long.class); - if (size != null) { - return size; - } - return 0; - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Provides a DATE-TIME value indicating the earliest date and time (in UTC) that the server is willing to accept - * for any DATE or DATE-TIME value in a calendar object resource stored in a calendar collection. - */ - public String getMinDateTime() { - try { - return getProperty(CalDavPropertyName.MIN_DATE_TIME, String.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Get the list of calendar components (VEVENT, VTODO, etc.) that this collection supports. - */ - public String[] getSupportedComponentTypes() { - List supportedComponents = new ArrayList(); - - ArrayList supportedCalCompSetProp; - try { - supportedCalCompSetProp = getProperty(CalDavPropertyName.SUPPORTED_CALENDAR_COMPONENT_SET, ArrayList.class); - if (supportedCalCompSetProp != null) { - for (Node child : supportedCalCompSetProp) { - if (child instanceof Element) { - Node nameNode = child.getAttributes().getNamedItem("name"); - if (nameNode != null) { - supportedComponents.add(nameNode.getTextContent()); - } - } - } - } - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - - return supportedComponents.toArray(new String[supportedComponents.size()]); - } - - /** - * The CALDAV:calendar-timezone property is used to specify the time zone the server should rely on to resolve - * "date" values and "date with local time" values (i.e., floating time) to "date with UTC time" values. - */ - public Calendar getTimeZone() { - try { - String calTimezoneProp = getProperty(CalDavPropertyName.CALENDAR_TIMEZONE, String.class); - - if (calTimezoneProp != null) { - CalendarBuilder builder = new CalendarBuilder(); - return builder.build(new StringReader(calTimezoneProp)); - } - return new Calendar(); - } catch (ObjectStoreException | IOException | DavException | ParserException e) { - throw new RuntimeException(e); - } - } - - public String getColor() { - try { - return getProperty(ICalPropertyName.CALENDAR_COLOR, String.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - public int getOrder() { - try { - return getProperty(ICalPropertyName.CALENDAR_ORDER, Integer.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Add a new calendar object in the collection. Creation will be done on the server right away. - */ - @Override - public void addCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { - writeCalendarOnServer(calendar, true); - } - - /** - * Stores the specified calendar in this collection, using the specified URI. - * @param uri the URI (relative to this collection's path) where the calendar is to be stored - * @param calendar a calendar object instance to be added to the collection - * @throws ObjectStoreException when an unexpected error occurs (implementation-specific) - */ - public void addCalendar(String uri, Calendar calendar) throws ObjectStoreException { - writeCalendarOnServer(uri, calendar, true); - } - - /** - * Update a calendar object in the collection. Update will be send to the server right away. - * @param calendar - * @throws ObjectStoreException - * @throws ConstraintViolationException - */ - public void updateCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { - writeCalendarOnServer(calendar, false); - } - - /** - * Update a calendar object in the collection. Update will be send to the server right away. - * @param calendar - * @throws ObjectStoreException - */ - public void updateCalendar(String uri, Calendar calendar) throws ObjectStoreException { - writeCalendarOnServer(uri, calendar, false); - } - - public void writeCalendarOnServer(Calendar calendar, boolean isNew) throws ObjectStoreException, ConstraintViolationException { - Uid uid = Calendars.getUid(calendar); - writeCalendarOnServer(defaultUriFromUid(uid.getValue()), calendar, isNew); - } - - public void writeCalendarOnServer(String uri, Calendar calendar, boolean isNew) throws ObjectStoreException { - String path = getPath(); - if (!path.endsWith("/")) { - path = path.concat("/"); - } - PutMethod putMethod = new PutMethod(path + uri); - // putMethod.setAllEtags(true); - if (isNew) { - putMethod.addHeader("If-None-Match", "*"); - } else { - putMethod.addHeader("If-Match", "*"); - } - // putMethod.setRequestBody(calendar); - - try { - putMethod.setCalendar(calendar); - } catch (Exception e) { - throw new ObjectStoreException("Invalid calendar", e); - } - - try { - // TODO: get ETag and Schedule-Tag headers and store them locally - HttpResponse httpResponse = getStore().getClient().execute(putMethod); - if ((httpResponse.getStatusLine().getStatusCode() != DavServletResponse.SC_CREATED) - && (httpResponse.getStatusLine().getStatusCode() != DavServletResponse.SC_NO_CONTENT)) { - throw new ObjectStoreException("Error creating calendar on server: " + httpResponse.getStatusLine()); - } - } catch (IOException ioe) { - throw new ObjectStoreException("Error creating calendar on server", ioe); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Calendar getCalendar(String uid) throws ObjectNotFoundException { - return getCalendarFromUri(defaultUriFromUid(uid)); - } - - /** - * Returns the calendar object located at the specified URI. - * @param uri the URI (relative to this collection's path) where the calendar is to be found - * @return a calendar object or null if no calendar exists under the specified URI - */ - public Calendar getCalendarFromUri(String uri) throws ObjectNotFoundException { - String path = getPath(); - if (!path.endsWith("/")) { - path = path.concat("/"); - } - GetMethod method = new GetMethod(path + uri); - HttpResponse httpResponse; - try { - httpResponse = getStore().getClient().execute(method); - } catch (IOException e) { - throw new RuntimeException(e); - } - if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_OK) { - try { - return method.getCalendar(httpResponse); - } catch (IOException | ParserException e) { - throw new RuntimeException(e); - } - } else if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_NOT_FOUND) { - throw new ObjectNotFoundException(String.format("Calendar not found: %s", uri)); - } - return null; - } - - /** - * {@inheritDoc} - */ - public Calendar removeCalendar(String uid) throws FailedOperationException, ObjectStoreException, ObjectNotFoundException { - return removeCalendarFromUri(defaultUriFromUid(uid)); - } - - /** - * @param uri the URI (relative to this collection's path) where the calendar is to be found - * @return the calendar that was successfully removed from the collection - * @throws ObjectStoreException where an unexpected error occurs - */ - public Calendar removeCalendarFromUri(String uri) throws FailedOperationException, ObjectStoreException, ObjectNotFoundException { - Calendar calendar = getCalendarFromUri(uri); - - HttpDelete deleteMethod = new HttpDelete(getPath() + "/" + uri); - HttpResponse httpResponse; - try { - httpResponse = getStore().getClient().execute(deleteMethod); - } catch (IOException e) { - throw new ObjectStoreException(e); - } - if (!deleteMethod.succeeded(httpResponse)) { - throw new FailedOperationException(httpResponse.getStatusLine().toString()); - } - - return calendar; - } - - /** - * {@inheritDoc} - */ - public final void merge(Calendar calendar) throws FailedOperationException, ObjectStoreException { - try { - Calendar[] uidCalendars = Calendars.split(calendar); - for (int i = 0; i < uidCalendars.length; i++) { - addCalendar(uidCalendars[i]); - } - } catch (ConstraintViolationException cve) { - throw new FailedOperationException("Invalid calendar format", cve); - } - } - - /** - * {@inheritDoc} - */ - public Calendar export() throws ObjectStoreException { - throw new UnsupportedOperationException("not implemented"); - } - - /** - * {@inheritDoc} - */ - public Calendar[] getComponents() throws ObjectStoreException { - return getComponentsByType(Component.VEVENT); - } - - /** - * Get a list of calendar objects of VEVENT type for a specific time period. - * - * @param startTime - * @param endTime - * @return - * @throws IOException - * @throws DavException - * @throws ParserConfigurationException - * @throws ParserException - */ - public Calendar[] getEventsForTimePeriod(DateTime startTime, DateTime endTime) - throws IOException, DavException, ParserConfigurationException, ParserException { - - DocumentBuilderFactory BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); - BUILDER_FACTORY.setNamespaceAware(true); - BUILDER_FACTORY.setIgnoringComments(true); - BUILDER_FACTORY.setIgnoringElementContentWhitespace(true); - BUILDER_FACTORY.setCoalescing(true); - - Document document = BUILDER_FACTORY.newDocumentBuilder().newDocument(); - - org.w3c.dom.Element calData = DomUtil.createElement(document, CalDavConstants.PROPERTY_CALENDAR_DATA, - CalDavConstants.CALDAV_NAMESPACE); - - org.w3c.dom.Element calFilter = DomUtil.createElement(document, CalDavConstants.PROPERTY_COMP_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - calFilter.setAttribute(CalDavConstants.ATTRIBUTE_NAME, Calendar.VCALENDAR); - org.w3c.dom.Element eventFilter = DomUtil.createElement(document, CalDavConstants.PROPERTY_COMP_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - eventFilter.setAttribute(CalDavConstants.ATTRIBUTE_NAME, Component.VEVENT); - org.w3c.dom.Element timeRange = DomUtil.createElement(document, CalDavConstants.PROPERTY_TIME_RANGE, - CalDavConstants.CALDAV_NAMESPACE); - - timeRange.setAttribute(CalDavConstants.ATTRIBUTE_START, startTime.toString()); - timeRange.setAttribute(CalDavConstants.ATTRIBUTE_END, endTime.toString()); - eventFilter.appendChild(timeRange); - calFilter.appendChild(eventFilter); - - return getObjectsByFilter(calFilter, calData); - } - - /** - * Returns a list of calendar objects by calling a REPORT method with a filter. You must pass - * a XML element as the argument, the element must contain the filter. For example, if you wish - * to filter objects to only get VEVENT objects you can build a filter like this: - * - * org.w3c.dom.Element calData = DomUtil.createElement(document, CalDavConstants.PROPERTY_CALENDAR_DATA, - CalDavConstants.CALDAV_NAMESPACE); - * - * org.w3c.dom.Element calFilter = DomUtil.createElement(document, CalDavConstants.PROPERTY_COMP_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - * calFilter.setAttribute(CalDavConstants.ATTRIBUTE_NAME, Calendar.VCALENDAR); - * org.w3c.dom.Element eventFilter = DomUtil.createElement(document, CalDavConstants.PROPERTY_COMP_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - * eventFilter.setAttribute(CalDavConstants.ATTRIBUTE_NAME, Component.VEVENT); - * calFilter.appendChild(eventFilter); - * collection.getObjectsByFilter(calFilter); - * - * Check the examples in rfc4791 - * - * @param filter - * @return - * @throws IOException - * @throws DavException - * @throws ParserConfigurationException - * @throws ParserException - */ - public Calendar[] getObjectsByFilter(org.w3c.dom.Element filter, org.w3c.dom.Element calData) - throws IOException, DavException, ParserConfigurationException, ParserException { - ArrayList events = new ArrayList(); - - ReportInfo rinfo = new ReportInfo(ReportType.register(CalDavConstants.PROPERTY_CALENDAR_QUERY, - CalDavConstants.CALDAV_NAMESPACE, - org.apache.jackrabbit.webdav.security.report.PrincipalMatchReport.class), 1); - - DocumentBuilderFactory BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); - BUILDER_FACTORY.setNamespaceAware(true); - BUILDER_FACTORY.setIgnoringComments(true); - BUILDER_FACTORY.setIgnoringElementContentWhitespace(true); - BUILDER_FACTORY.setCoalescing(true); - - Document document = BUILDER_FACTORY.newDocumentBuilder().newDocument(); - org.w3c.dom.Element property = DomUtil - .createElement(document, DavConstants.XML_PROP, CalDavConstants.NAMESPACE); - property.appendChild(DomUtil.createElement(document, DavConstants.PROPERTY_GETETAG, CalDavConstants.NAMESPACE)); - - Node importedCalData = document.importNode(calData, true); - property.appendChild(importedCalData); - - document.appendChild(property); - rinfo.setContentElement(property); - - org.w3c.dom.Element parentFilter = DomUtil.createElement(document, CalDavConstants.PROPERTY_FILTER, - CalDavConstants.CALDAV_NAMESPACE); - rinfo.setContentElement(parentFilter); - - Node importedFilter = document.importNode(filter, true); - parentFilter.appendChild(importedFilter); - - ReportMethod method = new ReportMethod(this.getPath(), rinfo); - HttpResponse httpResponse = this.getStore().getClient().execute(method); - MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(httpResponse); - MultiStatusResponse[] responses = multiStatus.getResponses(); - for (int i = 0; i < responses.length; i++) { - for (int j = 0; j < responses[i].getStatus().length; j++) { - Status status = responses[i].getStatus()[j]; - for (DavPropertyIterator iNames = responses[i].getProperties(status.getStatusCode()).iterator(); iNames - .hasNext();) { - DavProperty name = iNames.nextProperty(); - if (name.getValue() instanceof String) { - if ((name.getName().getNamespace().equals(CalDavConstants.CALDAV_NAMESPACE)) - && (name.getName().getName().equals(CalDavConstants.PROPERTY_CALENDAR_DATA))) { - StringReader sin = new StringReader((String) name.getValue()); - CalendarBuilder builder = new CalendarBuilder(); - Calendar calendar = builder.build(sin); - events.add(calendar); - } - } - } - } - } - return events.toArray(new Calendar[events.size()]); - } - - /** - * TODO: implement calendar-multiget to fetch objects based on href - * @param hrefs - * @param calData - * @return - * @throws IOException - * @throws DavException - * @throws ParserConfigurationException - * @throws ParserException - */ - public Calendar[] getObjectsByMultiget(ArrayList hrefs, org.w3c.dom.Element calData) - throws IOException, DavException, ParserConfigurationException, ParserException { - return new Calendar[0]; - } - - /** - * TODO: implement free-busy-query - * @return - */ - public Calendar[] doFreeBusyQuery() { - return new Calendar[0]; - } - - public static final DavPropertyNameSet propertiesForFetch() { - DavPropertyNameSet principalsProps = new DavPropertyNameSet(); - - principalsProps.add(BaseDavPropertyName.QUOTA_AVAILABLE_BYTES); - principalsProps.add(BaseDavPropertyName.QUOTA_USED_BYTES); - principalsProps.add(BaseDavPropertyName.CURRENT_USER_PRIVILEGE_SET); - principalsProps.add(BaseDavPropertyName.PROP); - principalsProps.add(BaseDavPropertyName.RESOURCETYPE); - principalsProps.add(DavPropertyName.DISPLAYNAME); - principalsProps.add(SecurityConstants.OWNER); - - principalsProps.add(CalDavPropertyName.CALENDAR_DESCRIPTION); - principalsProps.add(CalDavPropertyName.SUPPORTED_CALENDAR_COMPONENT_SET); - principalsProps.add(CalDavPropertyName.FREE_BUSY_SET); - principalsProps.add(CalDavPropertyName.SCHEDULE_CALENDAR_TRANSP); - principalsProps.add(CalDavPropertyName.SCHEDULE_DEFAULT_CALENDAR_URL); - principalsProps.add(CalDavPropertyName.CALENDAR_TIMEZONE); - principalsProps.add(CalDavPropertyName.SUPPORTED_CALENDAR_DATA); - principalsProps.add(CalDavPropertyName.MAX_ATTENDEES_PER_INSTANCE); - principalsProps.add(CalDavPropertyName.MAX_DATE_TIME); - principalsProps.add(CalDavPropertyName.MIN_DATE_TIME); - principalsProps.add(CalDavPropertyName.MAX_INSTANCES); - principalsProps.add(CalDavPropertyName.MAX_RESOURCE_SIZE); - - principalsProps.add(CSDavPropertyName.XMPP_SERVER); - principalsProps.add(CSDavPropertyName.XMPP_URI); - principalsProps.add(CSDavPropertyName.CTAG); - principalsProps.add(CSDavPropertyName.SOURCE); - principalsProps.add(CSDavPropertyName.SUBSCRIBED_STRIP_ALARMS); - principalsProps.add(CSDavPropertyName.SUBSCRIBED_STRIP_ATTACHMENTS); - principalsProps.add(CSDavPropertyName.SUBSCRIBED_STRIP_TODOS); - principalsProps.add(CSDavPropertyName.REFRESHRATE); - principalsProps.add(CSDavPropertyName.PUSH_TRANSPORTS); - principalsProps.add(CSDavPropertyName.PUSHKEY); - - principalsProps.add(ICalPropertyName.CALENDAR_COLOR); - principalsProps.add(ICalPropertyName.CALENDAR_ORDER); - - return principalsProps; - } - - @Override - public String toString() { - return "Display Name: " + getDisplayName() + ", id: " + getId(); - } - - - private String defaultUriFromUid(String uid) { - return uid + ".ics"; - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStore.java b/src/main/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStore.java deleted file mode 100644 index 2709956c..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/CalDavCalendarStore.java +++ /dev/null @@ -1,721 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -import net.fortuna.ical4j.connector.CalendarCollection; -import net.fortuna.ical4j.connector.CalendarStore; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.method.PrincipalPropertySearchInfo; -import net.fortuna.ical4j.connector.dav.method.PrincipalPropertySearchMethod; -import net.fortuna.ical4j.connector.dav.property.CalDavPropertyName; -import net.fortuna.ical4j.connector.dav.response.PropFindResponseHandler; -import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.model.Calendar; -import net.fortuna.ical4j.model.component.VFreeBusy; -import net.fortuna.ical4j.model.parameter.Cn; -import net.fortuna.ical4j.model.parameter.CuType; -import net.fortuna.ical4j.model.property.*; -import net.fortuna.ical4j.util.FixedUidGenerator; -import net.fortuna.ical4j.util.UidGenerator; -import org.apache.http.HttpResponse; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.MultiStatusResponse; -import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest; -import org.apache.jackrabbit.webdav.client.methods.HttpPropfind; -import org.apache.jackrabbit.webdav.client.methods.HttpReport; -import org.apache.jackrabbit.webdav.property.*; -import org.apache.jackrabbit.webdav.security.SecurityConstants; -import org.apache.jackrabbit.webdav.version.DeltaVConstants; -import org.apache.jackrabbit.webdav.version.report.ReportInfo; -import org.apache.jackrabbit.webdav.version.report.ReportType; -import org.apache.jackrabbit.webdav.xml.DomUtil; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.text.ParseException; -import java.util.*; -import java.util.stream.Collectors; - -import static net.fortuna.ical4j.connector.dav.enums.ResourceType.*; - -/** - * $Id$ - * - * Created on 24/02/2008 - * - * @author Ben - * - */ -public final class CalDavCalendarStore extends AbstractDavObjectStore implements - CalendarStore { - - private final String prodId; - private String displayName; - - /** - * @param prodId application product identifier - * @param url the URL of a CalDAV server instance - * @param pathResolver the path resolver for the CalDAV server type - */ - public CalDavCalendarStore(String prodId, URL url, PathResolver pathResolver) { - super(url, pathResolver); - this.prodId = prodId; - } - - /** - * {@inheritDoc} - */ - public CalDavCalendarCollection addCollection(String id) throws ObjectStoreException { - CalDavCalendarCollection collection = new CalDavCalendarCollection(this, id); - try { - collection.create(); - } catch (IOException e) { - throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); - } - return collection; - } - - /** - * {@inheritDoc} - */ - public CalDavCalendarCollection addCollection(String id, String displayName, String description, - String[] supportedComponents, Calendar timezone) throws ObjectStoreException { - - CalDavCalendarCollection collection = new CalDavCalendarCollection(this, id, displayName, description); - try { - collection.create(); - } catch (IOException e) { - throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); - } - return collection; - } - - /** - * {@inheritDoc} - */ - public CalDavCalendarCollection addCollection(String id, DavPropertySet properties) throws ObjectStoreException { - CalDavCalendarCollection collection = new CalDavCalendarCollection(this, id, properties); - try { - collection.create(); - } catch (IOException e) { - throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); - } - return collection; - } - - /** - * {@inheritDoc} - */ - public CalDavCalendarCollection getCollection(String id) throws ObjectStoreException, ObjectNotFoundException { - try { - DavPropertyNameSet principalsProps = CalDavCalendarCollection.propertiesForFetch(); - HttpPropfind getMethod = new HttpPropfind(id, principalsProps, 0); - - PropFindResponseHandler responseHandler = new PropFindResponseHandler(getMethod); - responseHandler.accept(this.getClient().execute(getMethod)); - if (!responseHandler.exists()) { - throw new ObjectNotFoundException(); - } - return responseHandler.getCollections( - Arrays.asList(CALENDAR, CALENDAR_PROXY_READ, CALENDAR_PROXY_WRITE)).entrySet().stream() - .map(e -> new CalDavCalendarCollection(this, e.getKey(), e.getValue())) - .collect(Collectors.toList()).get(0); - } catch (IOException | DavException e) { - throw new ObjectStoreException(String.format("unable to get collection '%s'", id), e); - } - } - - /** - * {@inheritDoc} - */ - public CalendarCollection merge(String id, CalendarCollection calendar) { - throw new UnsupportedOperationException("not implemented"); - } - - public String findCalendarHomeSet() throws ParserConfigurationException, IOException, DavException { - String propfindUri = getHostURL() + pathResolver.getPrincipalPath(getUserName()); - return findCalendarHomeSet(propfindUri); - } - - /** - * This method try to find the calendar-home-set attribute in the user's DAV principals. The calendar-home-set - * attribute is the URI of the main collection of calendars for the user. - * - * @return the URI for the main calendar collection - * @author Pascal Robert - * @throws ParserConfigurationException - * @throws IOException - * @throws DavException - */ - protected String findCalendarHomeSet(String propfindUri) throws IOException, DavException { - DavPropertyNameSet principalsProps = new DavPropertyNameSet(); - principalsProps.add(CalDavPropertyName.CALENDAR_HOME_SET); - principalsProps.add(DavPropertyName.DISPLAYNAME); - - HttpPropfind method = new HttpPropfind(propfindUri, principalsProps, 0); - PropFindResponseHandler responseHandler = new PropFindResponseHandler(method); - responseHandler.accept(getClient().execute(method)); - return responseHandler.getDavPropertyUri(CalDavPropertyName.CALENDAR_HOME_SET); - } - - /** - * This method will try to find all calendar collections available at the calendar-home-set URI of the user. - * - * @return An array of all available calendar collections - * @author Pascal Robert - * @throws ParserConfigurationException where the parse is not configured correctly - * @throws IOException where a communications error occurs - * @throws DavException where an error occurs calling the DAV method - */ - public List getCollections() throws ObjectStoreException, ObjectNotFoundException { - try { - String calHomeSetUri = findCalendarHomeSet(); - if (calHomeSetUri == null) { - throw new ObjectNotFoundException("No calendar-home-set attribute found for the user"); - } - String urlForcalendarHomeSet = getHostURL() + calHomeSetUri; - return getCollectionsForHomeSet(this, urlForcalendarHomeSet); - } catch (DavException de) { - throw new ObjectStoreException(de); - } catch (IOException ioe) { - throw new ObjectStoreException(ioe); - } catch (ParserConfigurationException pce) { - throw new ObjectStoreException(pce); - } - } - - protected List getCollectionsForHomeSet(CalDavCalendarStore store, - String urlForcalendarHomeSet) throws IOException, DavException { - List collections = new ArrayList(); - - DavPropertyNameSet principalsProps = CalDavCalendarCollection.propertiesForFetch(); - - HttpPropfind method = new HttpPropfind(urlForcalendarHomeSet, principalsProps, 1); - PropFindResponseHandler responseHandler = new PropFindResponseHandler(method); - responseHandler.accept(getClient().execute(method)); - - return responseHandler.getCollections( - Arrays.asList(CALENDAR, CALENDAR_PROXY_READ, CALENDAR_PROXY_WRITE)).entrySet().stream() - .map(e -> new CalDavCalendarCollection(this, e.getKey(), e.getValue())) - .collect(Collectors.toList()); - } - - @SuppressWarnings("unchecked") - protected List getDelegateCollections(DavProperty proxyDavProperty) - throws ParserConfigurationException, IOException, DavException { - - ArrayList delegatedCollections = new ArrayList(); - - /* - * Zimbra check: Zimbra advertise calendar-proxy, but it will return 404 in propstat if Enable delegation for - * Apple iCal CalDAV client is not enabled - */ - if (proxyDavProperty != null) { - Object propertyValue = proxyDavProperty.getValue(); - ArrayList response; - - if (propertyValue instanceof ArrayList) { - response = (ArrayList) proxyDavProperty.getValue(); - if (response != null) { - for (Node objectInArray: response) { - if (objectInArray instanceof Element) { - DefaultDavProperty newProperty = DefaultDavProperty - .createFromXml((Element) objectInArray); - if ((newProperty.getName().getName().equals((DavConstants.XML_RESPONSE))) - && (newProperty.getName().getNamespace().equals(DavConstants.NAMESPACE))) { - ArrayList responseChilds = (ArrayList) newProperty.getValue(); - for (Node responseChild : responseChilds) { - if (responseChild instanceof Element) { - DefaultDavProperty responseChildElement = DefaultDavProperty - .createFromXml((Element) responseChild); - if (responseChildElement.getName().getName().equals(DavConstants.XML_PROPSTAT)) { - ArrayList propStatChilds = (ArrayList) responseChildElement - .getValue(); - for (Node propStatChild : propStatChilds) { - if (propStatChild instanceof Element) { - DefaultDavProperty propStatChildElement = DefaultDavProperty - .createFromXml((Element) propStatChild); - if (propStatChildElement.getName().getName() - .equals(DavConstants.XML_PROP)) { - ArrayList propChilds = (ArrayList) propStatChildElement - .getValue(); - for (Node propChild : propChilds) { - if (propChild instanceof Element) { - DefaultDavProperty propChildElement = DefaultDavProperty - .createFromXml((Element) propChild); - if (propChildElement.getName().equals( - SecurityConstants.PRINCIPAL_URL)) { - ArrayList principalUrlChilds = (ArrayList) propChildElement - .getValue(); - for (Node principalUrlChild : principalUrlChilds) { - if (principalUrlChild instanceof Element) { - DefaultDavProperty principalUrlElement = DefaultDavProperty - .createFromXml((Element) principalUrlChild); - if (principalUrlElement.getName().getName() - .equals(DavConstants.XML_HREF)) { - String principalsUri = (String) principalUrlElement - .getValue(); - String urlForcalendarHomeSet = findCalendarHomeSet(getHostURL() - + principalsUri); - delegatedCollections.addAll(getCollectionsForHomeSet(this,urlForcalendarHomeSet)); - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - return delegatedCollections; - } - - /** - * Get the list of available delegated collections, Apple's iCal style - * - * @return - * @throws Exception - */ - public List getDelegatedCollections() throws Exception { - List collections = new ArrayList(); - collections.addAll(getWriteDelegatedCollections()); - collections.addAll(getReadOnlyDelegatedCollections()); - return collections; - } - - protected List getDelegatedCollections(String type) throws Exception { - List collections = new ArrayList(); - - String methodUri = this.pathResolver.getPrincipalPath(getUserName()); - - Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - - Element writeDisplayNameProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - writeDisplayNameProperty.setAttribute("name", DavConstants.PROPERTY_DISPLAYNAME); - - Element writePrincipalUrlProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - writePrincipalUrlProperty.setAttribute("name", SecurityConstants.PRINCIPAL_URL.getName()); - - Element writeUserAddressSetProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - writeUserAddressSetProperty.setAttribute("name", CalDavConstants.PROPERTY_USER_ADDRESS_SET); - writeUserAddressSetProperty.setAttribute("namespace", CalDavConstants.CALDAV_NAMESPACE.getURI()); - - Element proxyWriteForElement = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - proxyWriteForElement.setAttribute("name", type); - proxyWriteForElement.setAttribute("namespace", CalDavConstants.CS_NAMESPACE.getURI()); - proxyWriteForElement.appendChild(writeDisplayNameProperty); - proxyWriteForElement.appendChild(writePrincipalUrlProperty); - proxyWriteForElement.appendChild(writeUserAddressSetProperty); - - ReportInfo rinfo = new ReportInfo(ReportType.register(DeltaVConstants.XML_EXPAND_PROPERTY, - DeltaVConstants.NAMESPACE, org.apache.jackrabbit.webdav.version.report.ExpandPropertyReport.class), 0); - rinfo.setContentElement(proxyWriteForElement); - - BaseDavRequest method = new HttpReport(methodUri, rinfo); - HttpResponse httpResponse = getClient().execute(method); - - if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_MULTI_STATUS) { - MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(httpResponse); - MultiStatusResponse[] responses = multiStatus.getResponses(); - for (int i = 0; i < responses.length; i++) { - DavPropertySet properties = responses[i].getProperties(DavServletResponse.SC_OK); - DavProperty writeForProperty = properties.get(CalDavConstants.PROPERTY_PROXY_WRITE_FOR, - CalDavConstants.CS_NAMESPACE); - List writeCollections = getDelegateCollections(writeForProperty); - for (CalDavCalendarCollection writeCollection: writeCollections) { - writeCollection.setReadOnly(false); - collections.add(writeCollection); - } - DavProperty readForProperty = properties.get(CalDavConstants.PROPERTY_PROXY_READ_FOR, - CalDavConstants.CS_NAMESPACE); - List readCollections = getDelegateCollections(readForProperty); - for (CalDavCalendarCollection readCollection: readCollections) { - readCollection.setReadOnly(true); - collections.add(readCollection); - } - } - } - return collections; - } - - public List getWriteDelegatedCollections() throws Exception { - List collections = getDelegatedCollections(CalDavConstants.PROPERTY_PROXY_WRITE_FOR); - return collections; - } - - public List getReadOnlyDelegatedCollections() throws Exception { - List collections = getDelegatedCollections(CalDavConstants.PROPERTY_PROXY_READ_FOR); - return collections; - } - - /** - * {@inheritDoc} - */ - public CalDavCalendarCollection removeCollection(String id) throws ObjectStoreException, ObjectNotFoundException { - CalDavCalendarCollection collection = getCollection(id); - try { - collection.delete(); - } catch (IOException e) { - throw new ObjectStoreException(String.format("unable to remove collection '%s'", id), e); - } - return collection; - } - - // public CalendarCollection replace(String id, CalendarCollection calendar) { - // // TODO Auto-generated method stub - // return null; - // } - - /** - * @return the prodId - */ - final String getProdId() { - return prodId; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public String findScheduleOutbox() throws ParserConfigurationException, IOException, DavException { - return findInboxOrOutbox(CalDavPropertyName.SCHEDULE_OUTBOX_URL); - } - - public String findScheduleInbox() throws ParserConfigurationException, IOException, DavException { - return findInboxOrOutbox(CalDavPropertyName.SCHEDULE_INBOX_URL); - } - - protected String findInboxOrOutbox(DavPropertyName type) throws ParserConfigurationException, IOException, DavException { - String propfindUri = getClient().hostConfiguration.toURI() + pathResolver.getPrincipalPath(getUserName()); - - DavPropertyNameSet principalsProps = new DavPropertyNameSet(); - principalsProps.add(type); - - HttpPropfind method = new HttpPropfind(propfindUri, principalsProps, 0); - RequestConfig config = RequestConfig.copy(method.getConfig()).setAuthenticationEnabled(true).build(); - method.setConfig(config); - - PropFindResponseHandler responseHandler = new PropFindResponseHandler(method); - responseHandler.accept(getClient().execute(method)); - return responseHandler.getDavPropertyUri(type); - } - - /** - * This method will return free-busy information for each attendee. If the free-busy information can't be retrieve - * (for example, users on an foreign server), check the isSuccess method to see if free-busy lookup was successful. - * - * @author probert - */ - public ArrayList findFreeBusyInfoForAttendees(Organizer organizer, ArrayList attendees, - DtStart startTime, DtEnd endTime) throws ParserConfigurationException, IOException, DavException, - ParseException, ParserException, SAXException { - Random ramdomizer = new Random(); - ArrayList responses = new ArrayList(); - - HttpPost postMethod = new HttpPost(findScheduleOutbox()); - postMethod.addHeader(DavConstants.HEADER_CONTENT_TYPE, "text/calendar; charset=utf-8"); - - Calendar calendar = new Calendar(); - calendar.getProperties().add(new ProdId(getProdId())); - calendar.getProperties().add(Version.VERSION_2_0); - calendar.getProperties().add(CalScale.GREGORIAN); - calendar.getProperties().add(Method.REQUEST); - - VFreeBusy fbComponent = new VFreeBusy(); - - fbComponent.getProperties().add(organizer); - // Was removed from the draft, but some servers still need it - postMethod.addHeader("Originator", organizer.getValue()); - - fbComponent.getProperties().add(startTime); - fbComponent.getProperties().add(endTime); - - String strAttendee = ""; - if (attendees != null) { - for (Iterator itrAttendee = attendees.iterator(); itrAttendee.hasNext();) { - Attendee attendee = itrAttendee.next(); - fbComponent.getProperties().add(attendee); - strAttendee += attendee.getValue() + ","; - } - strAttendee = strAttendee.substring(0, strAttendee.length() - 1); - // Was removed from the draft, but some servers still need it - postMethod.addHeader("Recipient", strAttendee); - } - - UidGenerator ug = new FixedUidGenerator(ramdomizer.nextInt() + ""); - fbComponent.getProperties().add(ug.generateUid()); - calendar.getComponents().add(fbComponent); - - postMethod.setEntity(new StringEntity(calendar.toString())); - HttpResponse httpResponse = getClient().execute(postMethod); - if (httpResponse.getStatusLine().getStatusCode() < 300) { - DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance(); - xmlFactory.setNamespaceAware(true); - DocumentBuilder xmlBuilder = xmlFactory.newDocumentBuilder(); - Document xmlDoc = xmlBuilder.parse(postMethod.getEntity().getContent()); - NodeList nodes = xmlDoc.getElementsByTagNameNS(CalDavConstants.CALDAV_NAMESPACE.getURI(), - DavPropertyName.XML_RESPONSE); - for (int nodeItr = 0; nodeItr < nodes.getLength(); nodeItr++) { - responses.add(new ScheduleResponse((Element) nodes.item(nodeItr))); - } - } - return responses; - } - - public List getIndividuals(String nameToSearch) throws ParserConfigurationException, IOException, DavException, URISyntaxException { - return getUserTypes(CuType.INDIVIDUAL, nameToSearch); - } - - public List getRooms(String nameToSearch) throws ParserConfigurationException, IOException, DavException, URISyntaxException { - return getUserTypes(CuType.ROOM, nameToSearch); - } - - public List getAllRooms() throws ParserConfigurationException, IOException, DavException, URISyntaxException { - return getAllPrincipalsForType(CuType.ROOM); - } - - public List getAllResources() throws ParserConfigurationException, IOException, DavException, URISyntaxException { - return getAllPrincipalsForType(CuType.RESOURCE); - } - - protected Element propertiesForPropSearch(Document document) { - Element firstNameProperty = DomUtil.createElement(document, "first-name", CalDavConstants.CS_NAMESPACE); - Element recordTypeProperty = DomUtil.createElement(document, "record-type", CalDavConstants.CS_NAMESPACE); - Element calUserAddressSetProperty = DomUtil.createElement(document, CalDavConstants.PROPERTY_USER_ADDRESS_SET, CalDavConstants.CALDAV_NAMESPACE); - Element lastNameProperty = DomUtil.createElement(document, "last-name", CalDavConstants.CS_NAMESPACE); - Element principalUrlProperty = DomUtil.createElement(document, "principal-URL", CalDavConstants.NAMESPACE); - Element calUserTypeProperty = DomUtil.createElement(document, CalDavConstants.PROPERTY_USER_TYPE, CalDavConstants.CALDAV_NAMESPACE); - Element displayNameForProperty = DomUtil.createElement(document, "displayname", CalDavConstants.NAMESPACE); - Element emailAddressSetProperty = DomUtil.createElement(document, "email-address-set", CalDavConstants.CS_NAMESPACE); - - Element properties = DomUtil.createElement(document, "prop", DavConstants.NAMESPACE); - properties.appendChild(firstNameProperty); - properties.appendChild(recordTypeProperty); - properties.appendChild(calUserAddressSetProperty); - properties.appendChild(lastNameProperty); - properties.appendChild(principalUrlProperty); - properties.appendChild(calUserTypeProperty); - properties.appendChild(displayNameForProperty); - properties.appendChild(emailAddressSetProperty); - - return properties; - } - - public List getAllPrincipalsForType(CuType type) throws ParserConfigurationException, IOException, DavException, URISyntaxException { - Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - - Element displayName = DomUtil.createElement(document, "calendar-user-type", CalDavConstants.CALDAV_NAMESPACE); - - Element displayNameProperty = DomUtil.createElement(document, "prop", DavConstants.NAMESPACE); - displayNameProperty.appendChild(displayName); - - Element containsMatch = DomUtil.createElement(document, "match", DavConstants.NAMESPACE); - containsMatch.setAttribute("match-type", "equals"); - containsMatch.setTextContent(type.getValue()); - - Element propertySearchDisplayName = DomUtil.createElement(document, "property-search", DavConstants.NAMESPACE); - propertySearchDisplayName.appendChild(displayNameProperty); - propertySearchDisplayName.appendChild(containsMatch); - - Element properties = propertiesForPropSearch(document); - - Element principalPropSearch = DomUtil.createElement(document, "principal-property-search", DavConstants.NAMESPACE); - principalPropSearch.setAttribute("type", type.getValue()); - principalPropSearch.setAttribute("test", "anyof"); - principalPropSearch.appendChild(propertySearchDisplayName); - principalPropSearch.appendChild(properties); - - return executePrincipalPropSearch(principalPropSearch); - } - - /** - * Use this method to search for resources (individual, group, resource, room). - * For example, if you want to find all rooms that begins - * with "Room" in their email or email address, call this method with: - * - * getUserTypes(CuType.ROOM, "Room"); - * - * If nameToSearch is null, it will find all resources for the desired type. - * - * @param type - * @param nameToSearch - * @return - * @throws ParserConfigurationException - * @throws IOException - * @throws DavException - * @throws URISyntaxException - */ - protected List getUserTypes(CuType type, String nameToSearch) throws ParserConfigurationException, IOException, DavException, URISyntaxException { - Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - - Element displayName = DomUtil.createElement(document, "displayname", DavConstants.NAMESPACE); - - Element displayNameProperty = DomUtil.createElement(document, "prop", DavConstants.NAMESPACE); - displayNameProperty.appendChild(displayName); - - Element containsMatch = DomUtil.createElement(document, "match", DavConstants.NAMESPACE); - containsMatch.setAttribute("match-type", "contains"); - containsMatch.setTextContent(nameToSearch); - - Element propertySearchDisplayName = DomUtil.createElement(document, "property-search", DavConstants.NAMESPACE); - propertySearchDisplayName.appendChild(displayNameProperty); - propertySearchDisplayName.appendChild(containsMatch); - - Element emailAddressSet = DomUtil.createElement(document, "email-address-set", CalDavConstants.CS_NAMESPACE); - - Element emailSetProperty = DomUtil.createElement(document, "prop", DavConstants.NAMESPACE); - emailSetProperty.appendChild(emailAddressSet); - - Element startsWith = DomUtil.createElement(document, "match", DavConstants.NAMESPACE); - startsWith.setAttribute("match-type", "starts-with"); - if (startsWith != null) { - startsWith.setTextContent(nameToSearch); - } - - Element propertySearchEmail = DomUtil.createElement(document, "property-search", DavConstants.NAMESPACE); - propertySearchEmail.setTextContent(nameToSearch); - propertySearchEmail.appendChild(emailSetProperty); - propertySearchEmail.appendChild(startsWith); - - Element properties = propertiesForPropSearch(document); - - Element principalPropSearch = DomUtil.createElement(document, "principal-property-search", DavConstants.NAMESPACE); - principalPropSearch.setAttribute("type", type.getValue()); - principalPropSearch.setAttribute("test", "anyof"); - principalPropSearch.appendChild(propertySearchDisplayName); - principalPropSearch.appendChild(propertySearchEmail); - principalPropSearch.appendChild(properties); - - return executePrincipalPropSearch(principalPropSearch); - } - - protected List executePrincipalPropSearch(Element principalPropSearch) throws DavException, IOException, URISyntaxException { - PrincipalPropertySearchInfo rinfo = new PrincipalPropertySearchInfo(principalPropSearch, 0); - - String methodUri = this.pathResolver.getPrincipalPath(getUserName()); - BaseDavRequest method = new PrincipalPropertySearchMethod(methodUri, rinfo); - HttpResponse httpResponse = getClient().execute(method); - - List resources = new ArrayList(); - - if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_MULTI_STATUS) { - MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(httpResponse); - MultiStatusResponse[] responses = multiStatus.getResponses(); - for (int i = 0; i < responses.length; i++) { - - Attendee resource = new Attendee(); - DavPropertySet propertiesInResponse = responses[i].getProperties(DavServletResponse.SC_OK); - - DavProperty displayNameFromResponse = propertiesInResponse.get("displayname", - CalDavConstants.NAMESPACE); - if ((displayNameFromResponse != null) && (displayNameFromResponse.getValue() != null)) { - resource.getParameters().add(new Cn((String)displayNameFromResponse.getValue())); - } - - DavProperty emailSet = propertiesInResponse.get("email-address-set", - CalDavConstants.CS_NAMESPACE); - - if (emailSet != null && emailSet.getValue() != null) { - Object emailSetValue = emailSet.getValue(); - if (emailSetValue instanceof java.util.ArrayList) { - for (Object email: (java.util.ArrayList)emailSetValue) { - if (email instanceof org.w3c.dom.Node) { - String emailAddress = ((org.w3c.dom.Node)email).getTextContent(); - if (emailAddress != null && emailAddress.trim().length() > 0) { - if (!emailAddress.startsWith("mailto:")) { - emailAddress = "mailto:".concat(emailAddress); - } - resource.setCalAddress(new URI(emailAddress)); - } - } - } - } - } else { - DavProperty calendarUserAddressSet = propertiesInResponse.get(CalDavConstants.PROPERTY_USER_ADDRESS_SET, - CalDavConstants.CALDAV_NAMESPACE); - if (calendarUserAddressSet != null && calendarUserAddressSet.getValue() != null) { - Object value = calendarUserAddressSet.getValue(); - if (value instanceof java.util.ArrayList) { - for (Object addressSet: (java.util.ArrayList)value) { - if (addressSet instanceof org.w3c.dom.Node) { - String url = ((org.w3c.dom.Node)addressSet).getTextContent(); - if (url.startsWith("urn:uuid")) { - resource.setCalAddress(new URI(url)); - } - } - } - } - } - } - - DavProperty calendarUserType = propertiesInResponse.get(CalDavConstants.PROPERTY_USER_TYPE, - CalDavConstants.CALDAV_NAMESPACE); - if ((calendarUserType != null) && (calendarUserType.getValue() != null)) { - resource.getParameters().add(new CuType((String)calendarUserType.getValue())); - } - - resources.add(resource); - } - } - return resources; - } - -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/CalDavConstants.java b/src/main/java/net/fortuna/ical4j/connector/dav/CalDavConstants.java deleted file mode 100644 index b94331c8..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/CalDavConstants.java +++ /dev/null @@ -1,532 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -import org.apache.jackrabbit.webdav.xml.Namespace; - -/** - * $Id$ - * - * Created on 19/11/2008 - * - * @author Ben - * - */ -public interface CalDavConstants extends DavConstants { - - /** - * Default namespace. - */ - public static final Namespace CALDAV_NAMESPACE = Namespace.getNamespace("C", "urn:ietf:params:xml:ns:caldav"); - - /** - * Namespace used by CalendarServer (calendarserver.org). - */ - public static final Namespace CS_NAMESPACE = Namespace.getNamespace("S", "http://calendarserver.org/ns/"); - - /** - * Namespace used by the iCal client from Apple. - */ - public static final Namespace ICAL_NAMESPACE = Namespace.getNamespace("I", "http://apple.com/ns/ical/"); - - /** - * CardDAV namespace - */ - public static final Namespace CARDDAV_NAMESPACE = Namespace.getNamespace("C", "urn:ietf:params:xml:ns:carddav"); - - /** - * To improve on performance, this specification defines a new "calendar collection entity tag" (CTag) WebDAV - * property that is defined on calendar collections. When the calendar collection changes, the CTag value changes. - * Source : https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt - */ - public static final String PROPERTY_CTAG = "getctag"; - - /** - * Purpose: Provides a human-readable description of the calendar collection. RFC : rfc4791 - */ - public static final String PROPERTY_CALENDAR_DESCRIPTION = "calendar-description"; - - /** - * Apple's iCal client use this property to store the color of the calendar, set by the user in iCal. - */ - public static final String PROPERTY_CALENDAR_COLOR = "calendar-color"; - - /** - * - */ - public static final String PROPERTY_CALENDAR_ORDER = "calendar-order"; - - /** - * Purpose: Identify the calendars that contribute to the free-busy information for the owner of the scheduling - * https://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04 - * - * THIS PROPERTY WAS REMOVED IN DRAFT 05 AND THE OFFICIAL RFC (6638) - */ - public static final String PROPERTY_FREE_BUSY_SET = "calendar-free-busy-set"; - - /** - * Purpose: Identifies the URL of any WebDAV collections that contain calendar collections owned by the associated - * principal resource. RFC : rfc4791 - */ - public static final String PROPERTY_CALENDAR_HOME_SET = "calendar-home-set"; - - /** - * The property to identify a "calendar" resource-type for the collection. - */ - public static final String PROPERTY_RESOURCETYPE_CALENDAR = "calendar"; - - /** - * TODO: description missing. Stuff coming from Apple's Calendar Server - */ - public static final String PROPERTY_PROXY_WRITE_FOR = "calendar-proxy-write-for"; - - /** - * TODO: description missing. Stuff coming from Apple's Calendar Server - */ - public static final String PROPERTY_PROXY_READ_FOR = "calendar-proxy-read-for"; - - /** - * Identify the calendar addresses of the associated principal resource. - * https://tools.ietf.org/html/rfc6638 - */ - public static final String PROPERTY_USER_ADDRESS_SET = "calendar-user-address-set"; - - /** - * Identifies the calendar user type of the associated principal resource. Its value is the same as the iCalendar "CUTYPE". - * https://tools.ietf.org/html/rfc6638 - */ - public static final String PROPERTY_USER_TYPE= "calendar-user-type"; - - /** - * Identify the URL of the scheduling Inbox collection owned by the associated principal resource. - * https://tools.ietf.org/html/rfc6638 - */ - public static final String PROPERTY_SCHEDULE_INBOX_URL = "schedule-inbox-URL"; - - /** - * Identify the URL of the scheduling Outbox collection owned by the associated principal resource. - * https://tools.ietf.org/html/rfc6638 - */ - public static final String PROPERTY_SCHEDULE_OUTBOX_URL = "schedule-outbox-URL"; - - /** - * Specific to CalendarServer, but I can't find the description - */ - public static final String PROPERTY_DROP_HOME_URL = "dropbox-home-URL"; - - /** - * Provides the URI of the pubsub node to subscribe to in order to receive a notification whenever a - * resource within this calendar home has changed. - * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-pubsubdiscovery.txt - */ - public static final String PROPERTY_XMPP_URI = "xmpp-uri"; - - /** - * Identify the URL of the notification collection owned by the associated principal resource. - * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt - */ - public static final String PROPERTY_NOTIFICATION_URL = "notification-URL"; - - /** - * Provides the hostname of the XMPP server a client should connect to for subscribing to notifications. - * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-pubsubdiscovery.txt - */ - public static final String PROPERTY_XMPP_SERVER = "xmpp-server"; - - /** - * Specifies the calendar component types (e.g., VEVENT, VTODO, etc.) that calendar object resources can contain in - * the calendar collection. - */ - public static final String PROPERTY_SUPPORTED_CALENDAR_COMPONENT_SET = "supported-calendar-component-set"; - - /** - * Specifies a supported component type (e.g., VEVENT, VTODO, etc.) - */ - public static final String PROPERTY_COMPONENT = "comp"; - - /** - * Determines whether the calendar object resources in a calendar collection will affect the owner's freebusy. - */ - public static final String PROPERTY_SCHEDULE_CALENDAR_TRANSP = "schedule-calendar-transp"; - - /** - * Specifies a default calendar for an attendee that will automatically have new scheduling messages deposited into - * it when they arrive. - */ - public static final String PROPERTY_SCHEDULE_DEFAULT_CALENDAR_URL = "schedule-default-calendar-URL"; - - /** - * The CALDAV:calendar-timezone property is used to specify the time zone the server should rely on to resolve - * "date" values and "date with local time" values (i.e., floating time) to "date with UTC time" values. - */ - public static final String PROPERTY_CALENDAR_TIMEZONE = "calendar-timezone"; - - /** - * Auto-accept is currently supported for location and resource records. Specific to Calendar/iCal Server - */ - public static final String PROPERTY_AUTO_SCHEDULE = "auto-schedule"; - - /** - */ - public static final String PROPERTY_SOURCE = "source"; - - /** - */ - public static final String PROPERTY_SUBSCRIBED_STRIP_ALARMS = "subscribed-strip-alarms"; - - /** - */ - public static final String PROPERTY_SUBSCRIBED_STRIP_ATTACHMENTS = "subscribed-strip-attachments"; - - /** - */ - public static final String PROPERTY_SUBSCRIBED_STRIP_TODOS = "subscribed-strip-todos"; - - /** - */ - public static final String PROPERTY_REFRESHRATE = "refreshrate"; - - /** - */ - public static final String PROPERTY_PUSH_TRANSPORTS = "push-transports"; - - /** - */ - public static final String PROPERTY_PUSHKEY = "pushkey"; - - /** - * - */ - public static final String PROPERTY_SUPPORTED_CALENDAR_DATA = "supported-calendar-data"; - - /** - * - */ - public static final String PROPERTY_MAX_RESOURCE_SIZE = "max-resource-size"; - - /** - * - */ - public static final String PROPERTY_MIN_DATE_TIME = "min-date-time"; - - /** - * - */ - public static final String PROPERTY_MAX_DATE_TIME = "max-date-time"; - - /** - * - */ - public static final String PROPERTY_MAX_INSTANCES = "max-instances"; - - /** - * - */ - public static final String PROPERTY_MAX_ATTENDEES_PER_INSTANCE = "max-attendees-per-instance"; - - /** - * - */ - public static final String PROPERTY_CALENDAR_DATA = "calendar-data"; - - /** - * - */ - public static final String PROPERTY_RECIPIENT = "recipient"; - - /** - * - */ - public static final String PROPERTY_REQUEST_STATUS = "request-status"; - - /** - * - */ - public static final String PROPERTY_CALENDAR_QUERY = "calendar-query"; - - /** - * - */ - public static final String PROPERTY_FILTER = "filter"; - - /** - * - */ - public static final String PROPERTY_COMP_FILTER = "comp-filter"; - - /** - * - */ - public static final String PROPERTY_TIME_RANGE = "time-range"; - - /** - * - */ - public static final String ATTRIBUTE_NAME = "name"; - - /** - * - */ - public static final String ATTRIBUTE_START = "start"; - - /** - * - */ - public static final String ATTRIBUTE_END = "end"; - - - /** - * Identifies the URL of any WebDAV collections that contain address book collections - * owned by the associated principal resource. rfc6352 - */ - public static final String PROPERTY_ADDRESSBOOK_HOME_SET = "addressbook-home-set"; - - /** - * Specifies what media types are allowed for address object resources in an address - * book collection. rfc6352 - */ - public static final String PROPERTY_SUPPORTED_ADDRESS_DATA = "supported-address-data"; - - /** - * for carddav - */ - public static final String PROPERTY_MAX_IMAGE_SIZE = "max-image-size"; - - /** - * Specifies one of the following: - * - * 1. The parts of an address object resource that should be - * returned by a given address book REPORT request, and the media - * type and version for the returned data; or - * - * 2. The content of an address object resource in a response to an - * address book REPORT request. - * - * RFC 6352 - */ - public static final String PROPERTY_ADDRESS_DATA = "address-data"; - - /** - * Servers MAY reject requests to create a - * scheduling object resource with an iCalendar "UID" property value - * already in use by another scheduling object resource owned by the - * same user in other calendar collections. Servers SHOULD report - * the URL of the scheduling object resource that is already making - * use of the same "UID" property value in the DAV:href element. - * - * https://tools.ietf.org/html/rfc6638 - */ - public static final String UNIQUE_SCHEDULING_OBJECT_RESOURCE = "unique-scheduling-object-resource"; - - /** - * All the calendar components in a - * scheduling object resource MUST contain the same "ORGANIZER" - * property value when present - * - * https://tools.ietf.org/html/rfc6638 - */ - public static final String SAME_ORGANIZER_IN_ALL_COMPONENTS = "same-organizer-in-all-components"; - - /** - * Servers MAY impose restrictions on modifications allowed by an "Organizer". - * - * https://tools.ietf.org/html/rfc6638 - */ - public static final String ALLOWED_ORGANIZER_SCHEDULING_OBJECT_CHANGE = "allowed-organizer-scheduling-object-change"; - - /** - * Servers MAY impose restrictions on modifications allowed by an "Attendee", - * subject to the allowed changes specified in Section 3.2.2.1 - * - * https://tools.ietf.org/html/rfc6638 - */ - public static final String ALLOWED_ATTENDEE_SCHEDULING_OBJECT_CHANGE = "allowed-attendee-scheduling-object-change"; - - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String DEFAULT_CALENDAR_NEEDED = "default-calendar-needed"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String VALID_SCHEDULE_DEFAULT_CALENDAR_URL = "valid-schedule-default-calendar-URL"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String VALID_SCHEDULING_MESSAGE = "valid-scheduling-message"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String VALID_ORGANIZER = "valid-organizer"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String SCHEDULE_DELIVER = "schedule-deliver"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String SCHEDULE_DELIVER_INVITE = "schedule-deliver-invite"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String SCHEDULE_DELIVER_REPLY = "schedule-deliver-reply"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String SCHEDULE_QUERY_FREEBUSY = "schedule-query-freebusy"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String SCHEDULE_SEND = "schedule-send"; - /** - * https://tools.ietf.org/html/rfc6638 - */ - public static final String SCHEDULE_SEND_INVITE = "schedule-send-invite"; - - /** - * The CALDAV:schedule-send-reply privilege controls the sending of - * scheduling messages by "Attendees". - * - * https://tools.ietf.org/html/rfc6638#section-6.2.3 - */ - public static final String SCHEDULE_SEND_REPLY = "schedule-send-reply"; - - /** - * The CALDAV:schedule-send-freebusy privilege controls the use of the - * POST method to submit scheduling messages that specify the scheduling - * method "REQUEST" with a "VFREEBUSY" calendar component. - * - * https://tools.ietf.org/html/rfc6638#section-6.2.4 - */ - public static final String SCHEDULE_SEND_FREEBUSY = "schedule-send-freebusy"; - - /** - * Determines whether the calendar object resources in a - * calendar collection will affect the owner's busy time information. - * - * https://tools.ietf.org/html/rfc6638#section-9.1 - */ - public static final String SCHEDULE_CALENDAR_TRANSP = "schedule-calendar-transp"; - - /** - * Specifies a default calendar for an "Attendee" where new - * scheduling object resources are created. - * - * https://tools.ietf.org/html/rfc6638#section-9.2 - */ - public static final String SCHEDULE_DEFAULT_CALENDAR_URL = "schedule-default-calendar-URL"; - - /** - * Indicates whether a scheduling object resource has had a - * "consequential" change made to it. - * - * https://tools.ietf.org/html/rfc6638#section-9.3 - */ - public static final String SCHEDULE_TAG = "schedule-tag"; - - /** - * Contains the set of responses for a POST method request (for scheduling) - * - * https://tools.ietf.org/html/rfc6638#section-10.1 - */ - public static final String SCHEDULE_RESPONSE = "schedule-response"; - - /** - * Contains a single response for a POST method request (for scheduling) - * - * https://tools.ietf.org/html/rfc6638#section-10.2 - */ - public static final String RESPONSE = "response"; - - /** - * The calendar user address that the enclosing response for a POST method request is for. - * - * https://tools.ietf.org/html/rfc6638#section-10.3 - */ - public static final String RECIPIENT = "recipient"; - - /** - * The iTIP "REQUEST-STATUS" property value for a scheduling response. - * - * https://tools.ietf.org/html/rfc6638#section-10.4 - */ - public static final String REQUEST_STATUS = "request-status"; - - /** - * Defines a "VAVAILABILITY" component that will be used in calculating - * free-busy time when an iTIP free-busy request is targeted at the - * calendar user who owns the Inbox. - * - * http://tools.ietf.org/html/draft-daboo-calendar-availability-03 - */ - public static final String CALENDAR_AVAIBILITY = "calendar-availability"; - - /** - * Enumerates the sets of component restrictions the server is - * willing to allow the client to specify in MKCALENDAR or extended - * MKCOL requests. - * - * http://tools.ietf.org/html/draft-daboo-caldav-extensions-01 - */ - public static final String SUPPORTED_CALENDAR_COMPONENT_SETS = "supported-calendar-component-sets"; - - /** - * A default alarm applied to "VEVENT" components whose "DTSTART" property value - * type is "DATE-TIME" - * - * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 - */ - public static final String DEFAULT_ALARM_VEVENT_DATETIME = "default-alarm-vevent-datetime"; - - /** - * A default alarm applied to "VEVENT" components whose "DTSTART" property value type is "DATE" - * - * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 - */ - public static final String DEFAULT_ALARM_VEVENT_DATE = "default-alarm-vevent-date"; - - /** - * A default alarm applied to "VTODO" components whose "DUE" or "DTSTART" - * property value type is "DATE-TIME" - * - * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 - */ - public static final String DEFAULT_ALARM_VTODO_DATETIME = "default-alarm-vtodo-datetime"; - - /** - * A default alarm applied to "VTODO" components whose "DUE" or "DTSTART" - * property value type is "DATE", or when neither of those properties is present - * - * http://tools.ietf.org/html/draft-daboo-valarm-extensions-04 - */ - public static final String DEFAULT_ALARM_VTODO_DATE = "default-alarm-vtodo-date"; -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/CardDavCollection.java b/src/main/java/net/fortuna/ical4j/connector/dav/CardDavCollection.java deleted file mode 100644 index 533c69cf..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/CardDavCollection.java +++ /dev/null @@ -1,264 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -import net.fortuna.ical4j.connector.CardCollection; -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.method.MkCalendarMethod; -import net.fortuna.ical4j.connector.dav.method.PutMethod; -import net.fortuna.ical4j.connector.dav.method.ReportMethod; -import net.fortuna.ical4j.connector.dav.property.BaseDavPropertyName; -import net.fortuna.ical4j.connector.dav.property.CalDavPropertyName; -import net.fortuna.ical4j.connector.dav.property.CardDavPropertyName; -import net.fortuna.ical4j.model.Calendar; -import net.fortuna.ical4j.model.ConstraintViolationException; -import net.fortuna.ical4j.vcard.Property.Id; -import net.fortuna.ical4j.vcard.VCard; -import org.apache.http.HttpResponse; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.client.methods.XmlEntity; -import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; -import org.apache.jackrabbit.webdav.property.DavPropertySet; -import org.apache.jackrabbit.webdav.property.DefaultDavProperty; -import org.apache.jackrabbit.webdav.security.SecurityConstants; -import org.apache.jackrabbit.webdav.version.report.ReportInfo; - -import java.io.IOException; - -/** - * $Id$ - * - * Created on 24/02/2008 - * - * @author Ben - * - */ -public class CardDavCollection extends AbstractDavObjectCollection implements CardCollection { - - /** - * Only {@link CardDavStore} should be calling this, so default modifier is applied. - * - * @param CardDavCalendarStore - * @param path - */ - CardDavCollection(CardDavStore CardDavCalendarStore, String id) { - this(CardDavCalendarStore, id, null, null); - } - - /** - * Only {@link CardDavStore} should be calling this, so default modifier is applied. - * - * @param CardDavCalendarStore - * @param id - * @param displayName - * @param description - */ - CardDavCollection(CardDavStore CardDavCalendarStore, String id, String displayName, String description) { - - super(CardDavCalendarStore, id); - properties.add(new DefaultDavProperty(DavPropertyName.DISPLAYNAME, displayName)); - properties.add(new DefaultDavProperty(CalDavPropertyName.CALENDAR_DESCRIPTION, description)); - } - - CardDavCollection(CardDavStore CardDavCalendarStore, String id, DavPropertySet _properties) { - this(CardDavCalendarStore, id, null, null); - this.properties = _properties; - } - - /** - * Creates this collection on the CalDAV server. - * - * @throws IOException - * @throws ObjectStoreException - */ - final void create() throws IOException, ObjectStoreException { - MkCalendarMethod mkCalendarMethod = new MkCalendarMethod(getPath()); - - MkCalendar mkcalendar = new MkCalendar(); - mkcalendar.setProperties(properties); - System.out.println("properties: " + properties.getContentSize()); - mkCalendarMethod.setEntity(XmlEntity.create(mkcalendar)); - - HttpResponse httpResponse = getStore().getClient().execute(mkCalendarMethod); - if (!mkCalendarMethod.succeeded(httpResponse)) { - throw new ObjectStoreException(httpResponse.getStatusLine().getStatusCode() + ": " - + httpResponse.getStatusLine().getReasonPhrase()); - } - } - - /** - * Human-readable name of the collection. - */ - public String getDisplayName() { - try { - return getProperty(DavPropertyName.DISPLAYNAME, String.class); - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - } - - /** - * Provides a numeric value indicating the maximum size of a resource in octets that the server is willing to accept - * when a calendar object resource is stored in a calendar collection. 0 = no limits. - */ - public long getMaxResourceSize() { - try { - Long size = getProperty(CalDavPropertyName.MAX_RESOURCE_SIZE, Long.class); - if (size != null) { - return size; - } - } catch (ObjectStoreException | IOException | DavException e) { - throw new RuntimeException(e); - } - return 0; - } - - /** - * {@inheritDoc} - */ - public Calendar export() throws ObjectStoreException { - throw new UnsupportedOperationException("not implemented"); - } - - public static final DavPropertyNameSet propertiesForFetch() { - DavPropertyNameSet principalsProps = new DavPropertyNameSet(); - - /* - * TODO : to add the following properties - - - - - */ - - principalsProps.add(DavPropertyName.DISPLAYNAME); - - - principalsProps.add(BaseDavPropertyName.CURRENT_USER_PRIVILEGE_SET); - principalsProps.add(BaseDavPropertyName.RESOURCETYPE); - principalsProps.add(SecurityConstants.OWNER); - principalsProps.add(CardDavPropertyName.MAX_RESOURCE_SIZE); - principalsProps.add(BaseDavPropertyName.RESOURCE_ID); - principalsProps.add(BaseDavPropertyName.SUPPORTED_REPORT_SET); - principalsProps.add(BaseDavPropertyName.SYNC_TOKEN); - principalsProps.add(BaseDavPropertyName.ADD_MEMBER); - principalsProps.add(CardDavPropertyName.MAX_IMAGE_SIZE); - - /** - * FIXME jackrabbit generates an error when quota-used-bytes is sent. - * I suspect the problem is that the response have this attribute: e:dt="int" - */ - //principalsProps.add(BaseDavPropertyName.QUOTA_USED_BYTES); - //principalsProps.add(BaseDavPropertyName.QUOTA_AVAILABLE_BYTES); - - /* In the absence of this property, the server MUST only accept data with the media type - * "text/vcard" and vCard version 3.0, and clients can assume that is - * all the server will accept. - */ - principalsProps.add(CardDavPropertyName.SUPPORTED_ADDRESS_DATA); - - return principalsProps; - } - - /* (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectCollection#getDescription() - */ - public String getDescription() { - // TODO Auto-generated method stub - return null; - } - - /* (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectCollection#getComponents() - */ - public VCard[] getComponents() throws ObjectStoreException { - try { - DavPropertyNameSet properties = new DavPropertyNameSet(); - properties.add(DavPropertyName.GETETAG); - properties.add(CardDavPropertyName.ADDRESS_DATA); - - ReportInfo info = new ReportInfo(ReportMethod.ADDRESSBOOK_QUERY, 1, properties); - - ReportMethod method = new ReportMethod(getPath(), info); - HttpResponse httpResponse = getStore().getClient().execute(method); - if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_MULTI_STATUS) { - return method.getVCards(httpResponse); - } else if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_NOT_FOUND) { - return new VCard[0]; - } - } catch (IOException | DavException e) { - throw new RuntimeException(e); - } - return new VCard[0]; - } - - /* (non-Javadoc) - * @see net.fortuna.ical4j.connector.CardCollection#addCard(net.fortuna.ical4j.vcard.VCard) - */ - public void addCard(VCard card) throws ObjectStoreException, ConstraintViolationException { - net.fortuna.ical4j.vcard.property.Uid uid = (net.fortuna.ical4j.vcard.property.Uid)card.getProperty(Id.UID); - - String path = getPath(); - if (!path.endsWith("/")) { - path = path.concat("/"); - } - PutMethod putMethod = new PutMethod(path + uid.getValue() + ".vcf"); - // putMethod.setAllEtags(true); - // putMethod.setIfNoneMatch(true); - // putMethod.setRequestBody(calendar); - - try { - putMethod.setVCard(card); - } catch (Exception e) { - throw new ObjectStoreException("Invalid vcard", e); - } - - try { - HttpResponse httpResponse = getStore().getClient().execute(putMethod); - if ((httpResponse.getStatusLine().getStatusCode() != DavServletResponse.SC_CREATED) - && (httpResponse.getStatusLine().getStatusCode() != DavServletResponse.SC_NO_CONTENT)) { - throw new ObjectStoreException("Error creating calendar on server: " + httpResponse.getStatusLine()); - } - } catch (IOException ioe) { - throw new ObjectStoreException("Error creating calendar on server", ioe); - } - } - - @Override - public VCard removeCard(String uid) throws ObjectNotFoundException, FailedOperationException { - return null; - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/CardDavStore.java b/src/main/java/net/fortuna/ical4j/connector/dav/CardDavStore.java deleted file mode 100644 index 9ca83b16..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/CardDavStore.java +++ /dev/null @@ -1,409 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -import net.fortuna.ical4j.connector.CalendarCollection; -import net.fortuna.ical4j.connector.CardStore; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.enums.ResourceType; -import net.fortuna.ical4j.connector.dav.property.CardDavPropertyName; -import net.fortuna.ical4j.connector.dav.response.PropFindResponseHandler; -import net.fortuna.ical4j.model.Calendar; -import org.apache.http.HttpResponse; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.MultiStatusResponse; -import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest; -import org.apache.jackrabbit.webdav.client.methods.HttpPropfind; -import org.apache.jackrabbit.webdav.client.methods.HttpReport; -import org.apache.jackrabbit.webdav.property.*; -import org.apache.jackrabbit.webdav.security.SecurityConstants; -import org.apache.jackrabbit.webdav.version.DeltaVConstants; -import org.apache.jackrabbit.webdav.version.report.ReportInfo; -import org.apache.jackrabbit.webdav.version.report.ReportType; -import org.apache.jackrabbit.webdav.xml.DomUtil; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * $Id$ - * - * Created on 24/02/2008 - * - * @author Ben - * - */ -public final class CardDavStore extends AbstractDavObjectStore implements - CardStore { - - private final String prodId; - private String displayName; - - - /** - * @param prodId application product identifier - * @param url the URL of a CardDav server instance - */ - public CardDavStore( String prodId, URL url ) { - this( prodId, url, null ); - } - - - /** - * @param prodId application product identifier - * @param url the URL of a CardDav server instance - * @param pathResolver the path resolver for the CardDav server type - */ - public CardDavStore(String prodId, URL url, PathResolver pathResolver) { - super(url, pathResolver); - this.prodId = prodId; - } - - /** - * {@inheritDoc} - */ - public CardDavCollection addCollection(String id) throws ObjectStoreException { - CardDavCollection collection = new CardDavCollection(this, id); - try { - collection.create(); - } catch (IOException e) { - throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); - } - return collection; - } - - /** - * {@inheritDoc} - */ - public CardDavCollection addCollection(String id, DavPropertySet properties) throws ObjectStoreException { - CardDavCollection collection = new CardDavCollection(this, id, properties); - try { - collection.create(); - } catch (IOException e) { - throw new ObjectStoreException(String.format("unable to add collection '%s'", id), e); - } - return collection; - } - - /** - * {@inheritDoc} - */ - public CardDavCollection getCollection(String id) throws ObjectStoreException, ObjectNotFoundException { - try { - DavPropertyNameSet principalsProps = CardDavCollection.propertiesForFetch(); - HttpPropfind getMethod = new HttpPropfind(id, principalsProps, 0); - - PropFindResponseHandler responseHandler = new PropFindResponseHandler(getMethod); - responseHandler.accept(this.getClient().execute(getMethod)); - return responseHandler.getCollections(Collections.singletonList(ResourceType.ADRESSBOOK)).entrySet().stream() - .map(e -> new CardDavCollection(this, e.getKey(), e.getValue())) - .collect(Collectors.toList()).get(0); - } catch (IOException | DavException e) { - throw new ObjectStoreException(String.format("unable to get collection '%s'", id), e); - } - } - - /** - * {@inheritDoc} - */ - public CalendarCollection merge(String id, CalendarCollection calendar) { - throw new UnsupportedOperationException("not implemented"); - } - - protected String findAddressBookHomeSet() throws ParserConfigurationException, IOException, DavException { - String propfindUri = getHostURL() + pathResolver.getPrincipalPath(getUserName()); - return findAddressBookHomeSet(propfindUri); - } - - /** - * This method try to find the calendar-home-set attribute in the user's DAV principals. The calendar-home-set - * attribute is the URI of the main collection of calendars for the user. - * - * @return the URI for the main calendar collection - * @author Pascal Robert - * @throws ParserConfigurationException - * @throws IOException - * @throws DavException - */ - protected String findAddressBookHomeSet(String propfindUri) throws ParserConfigurationException, IOException, - DavException { - DavPropertyNameSet principalsProps = new DavPropertyNameSet(); - principalsProps.add(CardDavPropertyName.ADDRESSBOOK_HOME_SET); - principalsProps.add(DavPropertyName.DISPLAYNAME); - - HttpPropfind method = new HttpPropfind(propfindUri, principalsProps, 0); - PropFindResponseHandler responseHandler = new PropFindResponseHandler(method); - responseHandler.accept(getClient().execute(method)); - return responseHandler.getDavPropertyUri(CardDavPropertyName.ADDRESSBOOK_HOME_SET); - } - - /** - * This method will try to find all calendar collections available at the calendar-home-set URI of the user. - * - * @return An array of all available calendar collections - * @author Pascal Robert - * @throws ParserConfigurationException where the parse is not configured correctly - * @throws IOException where a communications error occurs - * @throws DavException where an error occurs calling the DAV method - */ - public List getCollections() throws ObjectStoreException, ObjectNotFoundException { - try { - String calHomeSetUri = findAddressBookHomeSet(); - if (calHomeSetUri == null) { - throw new ObjectNotFoundException("No " + CalDavConstants.PROPERTY_ADDRESSBOOK_HOME_SET + " attribute found for the user"); - } - String urlForcalendarHomeSet = getHostURL() + calHomeSetUri; - return getCollectionsForHomeSet(this, urlForcalendarHomeSet); - } catch (DavException | IOException | ParserConfigurationException e) { - throw new ObjectStoreException(e); - } - } - - protected List getCollectionsForHomeSet(CardDavStore store, - String urlForcalendarHomeSet) throws IOException, DavException { - List collections = new ArrayList(); - - DavPropertyNameSet principalsProps = CardDavCollection.propertiesForFetch(); - - HttpPropfind method = new HttpPropfind(urlForcalendarHomeSet, principalsProps, 1); - PropFindResponseHandler responseHandler = new PropFindResponseHandler(method); - responseHandler.accept(getClient().execute(method)); - return responseHandler.getCollections(Collections.singletonList(ResourceType.ADRESSBOOK)).entrySet().stream() - .map(e -> new CardDavCollection(this, e.getKey(), e.getValue())) - .collect(Collectors.toList()); - } - - protected List getDelegateCollections(DavProperty proxyDavProperty) - throws ParserConfigurationException, IOException, DavException { - /* - * Zimbra check: Zimbra advertise calendar-proxy, but it will return 404 in propstat if Enable delegation for - * Apple iCal CardDav client is not enabled - */ - if (proxyDavProperty != null) { - Object propertyValue = proxyDavProperty.getValue(); - ArrayList response; - - if (propertyValue instanceof ArrayList) { - response = (ArrayList) proxyDavProperty.getValue(); - if (response != null) { - for (Node objectInArray : response) { - if (objectInArray instanceof Element) { - DefaultDavProperty newProperty = DefaultDavProperty - .createFromXml((Element) objectInArray); - if ((newProperty.getName().getName().equals((DavConstants.XML_RESPONSE))) - && (newProperty.getName().getNamespace().equals(DavConstants.NAMESPACE))) { - ArrayList responseChilds = (ArrayList) newProperty.getValue(); - for (Node responseChild : responseChilds) { - if (responseChild instanceof Element) { - DefaultDavProperty responseChildElement = DefaultDavProperty - .createFromXml((Element) responseChild); - if (responseChildElement.getName().getName().equals(DavConstants.XML_PROPSTAT)) { - ArrayList propStatChilds = (ArrayList) responseChildElement - .getValue(); - for (Node propStatChild : propStatChilds) { - if (propStatChild instanceof Element) { - DefaultDavProperty propStatChildElement = DefaultDavProperty - .createFromXml((Element) propStatChild); - if (propStatChildElement.getName().getName() - .equals(DavConstants.XML_PROP)) { - ArrayList propChilds = (ArrayList) propStatChildElement - .getValue(); - for (Node propChild : propChilds) { - if (propChild instanceof Element) { - DefaultDavProperty propChildElement = DefaultDavProperty - .createFromXml((Element) propChild); - if (propChildElement.getName().equals( - SecurityConstants.PRINCIPAL_URL)) { - ArrayList principalUrlChilds = (ArrayList) propChildElement - .getValue(); - for (Node principalUrlChild : principalUrlChilds) { - if (principalUrlChild instanceof Element) { - DefaultDavProperty principalUrlElement = DefaultDavProperty - .createFromXml((Element) principalUrlChild); - if (principalUrlElement.getName().getName() - .equals(DavConstants.XML_HREF)) { - String principalsUri = (String) principalUrlElement - .getValue(); - String urlForcalendarHomeSet = findAddressBookHomeSet(getHostURL() - + principalsUri); - return getCollectionsForHomeSet(this, - urlForcalendarHomeSet); - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } else if (propertyValue instanceof Element) { - System.out.println(((Element)propertyValue).getNodeName()); - System.out.println(((Element)propertyValue).getChildNodes()); - } - } - return new ArrayList(); - } - - /** - * Get the list of available delegated collections, Apple's iCal style - * - * @return - * @throws Exception - */ - public List getDelegatedCollections() throws Exception { - - List collections = new ArrayList(); - - String methodUri = this.pathResolver.getPrincipalPath(getUserName()); - - Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - - Element writeDisplayNameProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - writeDisplayNameProperty.setAttribute("name", DavConstants.PROPERTY_DISPLAYNAME); - - Element writePrincipalUrlProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - writePrincipalUrlProperty.setAttribute("name", SecurityConstants.PRINCIPAL_URL.getName()); - - Element writeUserAddressSetProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - writeUserAddressSetProperty.setAttribute("name", CalDavConstants.PROPERTY_USER_ADDRESS_SET); - writeUserAddressSetProperty.setAttribute("namespace", CalDavConstants.NAMESPACE.getURI()); - - Element proxyWriteForElement = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - proxyWriteForElement.setAttribute("name", CalDavConstants.PROPERTY_PROXY_WRITE_FOR); - proxyWriteForElement.setAttribute("namespace", CalDavConstants.CS_NAMESPACE.getURI()); - proxyWriteForElement.appendChild(writeDisplayNameProperty); - proxyWriteForElement.appendChild(writePrincipalUrlProperty); - proxyWriteForElement.appendChild(writeUserAddressSetProperty); - - Element readDisplayNameProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - readDisplayNameProperty.setAttribute("name", DavConstants.PROPERTY_DISPLAYNAME); - - Element readPrincipalUrlProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - readPrincipalUrlProperty.setAttribute("name", SecurityConstants.PRINCIPAL_URL.getName()); - - Element readUserAddressSetProperty = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - readUserAddressSetProperty.setAttribute("name", CalDavConstants.PROPERTY_USER_ADDRESS_SET); - readUserAddressSetProperty.setAttribute("namespace", CalDavConstants.NAMESPACE.getURI()); - - Element proxyReadForElement = DomUtil.createElement(document, "property", DavConstants.NAMESPACE); - proxyReadForElement.setAttribute("name", CalDavConstants.PROPERTY_PROXY_READ_FOR); - proxyReadForElement.setAttribute("namespace", CalDavConstants.CS_NAMESPACE.getURI()); - proxyReadForElement.appendChild(readDisplayNameProperty); - proxyReadForElement.appendChild(readPrincipalUrlProperty); - proxyReadForElement.appendChild(readUserAddressSetProperty); - - ReportInfo rinfo = new ReportInfo(ReportType.register(DeltaVConstants.XML_EXPAND_PROPERTY, - DeltaVConstants.NAMESPACE, org.apache.jackrabbit.webdav.version.report.ExpandPropertyReport.class), 0); - rinfo.setContentElement(proxyWriteForElement); - rinfo.setContentElement(proxyReadForElement); - - BaseDavRequest method = new HttpReport(methodUri, rinfo); - HttpResponse httpResponse = getClient().execute(method); - - if (httpResponse.getStatusLine().getStatusCode() == DavServletResponse.SC_MULTI_STATUS) { - MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(httpResponse); - MultiStatusResponse[] responses = multiStatus.getResponses(); - for (int i = 0; i < responses.length; i++) { - DavPropertySet properties = responses[i].getProperties(DavServletResponse.SC_OK); - DavProperty writeForProperty = properties.get(CalDavConstants.PROPERTY_PROXY_WRITE_FOR, - CalDavConstants.CS_NAMESPACE); - collections.addAll(getDelegateCollections(writeForProperty)); - DavProperty readForProperty = properties.get(CalDavConstants.PROPERTY_PROXY_READ_FOR, - CalDavConstants.CS_NAMESPACE); - collections.addAll(getDelegateCollections(readForProperty)); - } - } - return collections; - } - - /** - * {@inheritDoc} - */ - public CardDavCollection removeCollection(String id) throws ObjectStoreException, ObjectNotFoundException { - CardDavCollection collection = getCollection(id); - try { - collection.delete(); - } catch (IOException e) { - throw new ObjectStoreException(String.format("unable to remove collection '%s'", id), e); - } - return collection; - } - - // public CalendarCollection replace(String id, CalendarCollection calendar) { - // // TODO Auto-generated method stub - // return null; - // } - - /** - * @return the prodId - */ - final String getProdId() { - return prodId; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - /* (non-Javadoc) - * @see net.fortuna.ical4j.connector.ObjectStore#addCollection(java.lang.String, java.lang.String, java.lang.String, java.lang.String[], net.fortuna.ical4j.model.Calendar) - */ - public CardDavCollection addCollection(String id, String displayName, String description, - String[] supportedComponents, Calendar timezone) throws ObjectStoreException { - throw new UnsupportedOperationException("not implemented"); - } - -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/DavClient.java b/src/main/java/net/fortuna/ical4j/connector/dav/DavClient.java deleted file mode 100644 index db1b3770..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/DavClient.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.dav.enums.SupportedFeature; -import net.fortuna.ical4j.connector.dav.property.CSDavPropertyName; -import net.fortuna.ical4j.connector.dav.response.PropFindResponseHandler; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.HttpClient; -import org.apache.http.client.config.AuthSchemes; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClients; -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.client.methods.HttpPropfind; -import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -public class DavClient { - - /** - * The underlying HTTP client. - */ - protected HttpClient httpClient; - - protected HttpClientContext httpClientContext; - - private String principalPath; - - private String userPath; - - private String bearerAuth; - - private CredentialsProvider credentialsProvider; - - private final boolean preemptiveAuth; - - /** - * The HTTP client configuration. - */ - protected HttpHost hostConfiguration; - - public DavClient(URL url, String principalPath, String userPath) { - this(url, principalPath, userPath, false); - } - - public DavClient(URL url, String principalPath, String userPath, boolean preemptiveAuth) { - this.principalPath = principalPath; - this.userPath = userPath; - this.preemptiveAuth = preemptiveAuth; - - hostConfiguration = new HttpHost(url.getHost(), url.getPort(), url.getProtocol()); - } - - void begin() { - httpClient = HttpClients.createDefault(); - } - - void begin(CredentialsProvider credentialsProvider) { - httpClient = HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).build(); - - httpClientContext = HttpClientContext.create(); - - if (preemptiveAuth) { - AuthCache authCache = new BasicAuthCache(); - authCache.put(hostConfiguration, new BasicScheme()); - httpClientContext.setAuthCache(authCache); - } - } - - public List begin(String bearerAuth) throws IOException, FailedOperationException { - this.bearerAuth = bearerAuth; - return getSupportedFeatures(); - } - - public List begin(String username, char[] password) throws IOException, FailedOperationException { - Credentials credentials = new UsernamePasswordCredentials(username, new String(password)); - - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(new AuthScope(hostConfiguration.getHostName(), hostConfiguration.getPort()), - credentials); - - this.credentialsProvider = credentialsProvider; - return getSupportedFeatures(); - } - - public List getSupportedFeatures() throws IOException, FailedOperationException { - begin(credentialsProvider); - - DavPropertyNameSet props = new DavPropertyNameSet(); - props.add(DavPropertyName.RESOURCETYPE); - props.add(CSDavPropertyName.CTAG); - DavPropertyName owner = DavPropertyName.create("owner", DavConstants.NAMESPACE); - props.add(owner); - - HttpPropfind aGet = new HttpPropfind(principalPath, DavConstants.PROPFIND_BY_PROPERTY, props, 0); - if (bearerAuth != null) { - aGet.addHeader("Authorization", "Bearer " + bearerAuth); - } - - RequestConfig.Builder builder = aGet.getConfig() == null ? RequestConfig.custom() : RequestConfig.copy(aGet.getConfig()); - builder.setAuthenticationEnabled(true); - if (credentialsProvider != null) { - // Added to support iCal Server, who don't support Basic auth at all, - // only Kerberos and Digest - List authPrefs = new ArrayList(2); - authPrefs.add(AuthSchemes.DIGEST); - authPrefs.add(AuthSchemes.BASIC); - builder.setTargetPreferredAuthSchemes(authPrefs); - } - RequestConfig config = builder.build(); - aGet.setConfig(config); - - PropFindResponseHandler responseHandler = new PropFindResponseHandler(aGet); - responseHandler.accept(httpClient.execute(hostConfiguration, aGet, httpClientContext)); - return responseHandler.getSupportedFeatures(); - } - - public HttpResponse execute(HttpRequestBase method) throws IOException { - return execute(hostConfiguration, method); - } - - public HttpResponse execute(HttpHost _hostConfiguration, HttpRequestBase method) throws IOException { - return httpClient.execute(_hostConfiguration, method, httpClientContext); - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/DavClientFactory.java b/src/main/java/net/fortuna/ical4j/connector/dav/DavClientFactory.java deleted file mode 100644 index 4e729c5b..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/DavClientFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.fortuna.ical4j.connector.dav; - -import java.net.URL; - -public class DavClientFactory { - - private final boolean preemptiveAuth; - - public DavClientFactory(boolean preemptiveAuth) { - this.preemptiveAuth = preemptiveAuth; - } - - public DavClient newInstance(URL url, String principalPath, String userPath) { - return new DavClient(url, principalPath, userPath, preemptiveAuth); - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/DavConstants.java b/src/main/java/net/fortuna/ical4j/connector/dav/DavConstants.java deleted file mode 100644 index c8e4f459..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/DavConstants.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -public interface DavConstants extends org.apache.jackrabbit.webdav.DavConstants { - - /** - * Indicates the maximum amount of additional storage available to be allocated to a resource. RFC 4331 - */ - public static final String PROPERTY_QUOTA_AVAILABLE_BYTES = "quota-available-bytes"; - - /** - * Contains the amount of storage counted against the quota on a resource. RFC 4331 - */ - public static final String PROPERTY_QUOTA_USED_BYTES = "quota-used-bytes"; - - /** - * The DAV:resource-id property is a REQUIRED property that enables clients to determine whether two bindings - * are to the same resource. rfc5842 - */ - public static final String PROPERTY_RESOURCE_ID = "resource-id"; - - /** - * This property identifies the reports that are supported by the resource. RFC 3253 - */ - public static final String PROPERTY_SUPPORTED_REPORT_SET = "supported-report-set"; - - /** - * Contains the value of the synchronization token as it would be returned by a - * DAV:sync-collection report RFC 6578 - */ - public static final String PROPERTY_SYNC_TOKEN = "sync-token"; - - /** - * DAV:add-member is a protected property (see [RFC4918], Section 15) defined on WebDAV collections, - * and contains the "Add-Member" URI for that collection. RFC 5995 - */ - public static final String PROPERTY_ADD_MEMBER = "add-member"; - -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/PathResolver.java b/src/main/java/net/fortuna/ical4j/connector/dav/PathResolver.java deleted file mode 100644 index 42fe8a78..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/PathResolver.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav; - -/** - * Implementations resolve host path elements. - * - * @author fortuna - * - * Created on: 02/04/2009 - * - * $Id$ - */ -public enum PathResolver { - - /** - * - */ - CHANDLER( "/dav/%s/", "/dav/users/%s"), - - RADICALE("/%s", "/%s/"), - - BAIKAL("/calendars/%s", "/calendars/%s/"), - - /** - * - */ - CGP("/CalDAV/", "/CalDAV/"), - - /** - * - */ - KMS("/caldav/", null), - - /** - * - */ - ZIMBRA("/principals/users/%s/", "/dav/%s/"), - - /** - * - */ - ICAL_SERVER("/principals/users/%s/", "/dav/%s/"), - - /** - * - */ - CALENDAR_SERVER("/dav/%s/", "/dav/%s/"), - - GCAL("/calendar/dav/%s/user/", "/calendar/dav/%s/events/"), - - SOGO("/SOGo/dav/%s/", "/SOGo/dav/%s/"), - - DAVICAL("/caldav.php/%s/", "/caldav.php/%s/"), - - BEDEWORK("/ucaldav/principals/users/%s/", "/ucaldav/users/%s/"), - - ORACLE_CS("/dav/principals/%s/", "/dav/home/%s/"), - - GENERIC("%s", "%s"); - - - private final String userPathBase; - - private final String principalPathBase; - - PathResolver(String principalPathBase, String userPathBase) { - this.principalPathBase = principalPathBase; - this.userPathBase = userPathBase; - } - - /** - * Resolves the path component for a user's calendar store URL. - * @param username a username - * @return the user path for a server implementation - */ - public String getUserPath(String username) { - return String.format(userPathBase, username); - } - - /** - * Resolves the path component for a principal URL. - * @param username a username - * @return the principal path for a server implementation - */ - public String getPrincipalPath(String username) { - return String.format(principalPathBase, username); - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/ResponseHandler.java b/src/main/java/net/fortuna/ical4j/connector/dav/ResponseHandler.java deleted file mode 100644 index c3bc9f19..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/ResponseHandler.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.fortuna.ical4j.connector.dav; - -import org.apache.http.HttpResponse; - -import java.util.function.Consumer; - -public interface ResponseHandler extends Consumer { -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/property/CSDavPropertyName.java b/src/main/java/net/fortuna/ical4j/connector/dav/property/CSDavPropertyName.java deleted file mode 100644 index a233ced4..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/property/CSDavPropertyName.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav.property; - -import net.fortuna.ical4j.connector.dav.CalDavConstants; - -import org.apache.jackrabbit.webdav.property.DavPropertyName; - -/** - * Properties that belongs to the CalendarServer namespace. - * - * @author probert - * - */ -public class CSDavPropertyName { - - /** - * To improve on performance, this specification defines a new "calendar collection entity tag" (CTag) WebDAV - * property that is defined on calendar collections. When the calendar collection changes, the CTag value changes. - * Source : https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt - */ - public static final DavPropertyName CTAG = DavPropertyName.create(CalDavConstants.PROPERTY_CTAG, - CalDavConstants.CS_NAMESPACE); - - /** - * TODO: description missing. Stuff coming from Apple's Calendar Server - */ - public static final DavPropertyName PROXY_WRITE_FOR = DavPropertyName.create( - CalDavConstants.PROPERTY_PROXY_WRITE_FOR, CalDavConstants.CS_NAMESPACE); - - /** - * TODO: description missing. Stuff coming from Apple's Calendar Server - */ - public static final DavPropertyName PROXY_READ_FOR = DavPropertyName.create( - CalDavConstants.PROPERTY_PROXY_READ_FOR, CalDavConstants.CS_NAMESPACE); - - /** - * Specific to CalendarServer, but I can't find the description - */ - public static final DavPropertyName DROP_HOME_URL = DavPropertyName.create(CalDavConstants.PROPERTY_DROP_HOME_URL, - CalDavConstants.CS_NAMESPACE); - - /** - * Provides the URI of the pubsub node to subscribe to in order to receive a notification whenever a resource within - * this calendar home has changed. - * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions - * /caldav-pubsubdiscovery.txt - */ - public static final DavPropertyName XMPP_URI = DavPropertyName.create(CalDavConstants.PROPERTY_XMPP_URI, - CalDavConstants.CS_NAMESPACE); - - /** - * Identify the URL of the notification collection owned by the associated principal resource. - * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt - */ - public static final DavPropertyName NOTIFICATION_URL = DavPropertyName.create( - CalDavConstants.PROPERTY_NOTIFICATION_URL, CalDavConstants.CS_NAMESPACE); - - /** - * Provides the hostname of the XMPP server a client should connect to for subscribing to notifications. - * http://svn.calendarserver - * .org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-pubsubdiscovery.txt - */ - public static final DavPropertyName XMPP_SERVER = DavPropertyName.create(CalDavConstants.PROPERTY_XMPP_SERVER, - CalDavConstants.CS_NAMESPACE); - - /** - * Auto-accept is currently supported for location and resource records. Specific to Calendar/iCal Server - */ - public static final DavPropertyName AUTO_SCHEDULE = DavPropertyName.create(CalDavConstants.PROPERTY_AUTO_SCHEDULE, - CalDavConstants.CS_NAMESPACE); - - /** - */ - public static final DavPropertyName SOURCE = DavPropertyName.create(CalDavConstants.PROPERTY_SOURCE, - CalDavConstants.CS_NAMESPACE); - - /** - */ - public static final DavPropertyName SUBSCRIBED_STRIP_ALARMS = DavPropertyName.create( - CalDavConstants.PROPERTY_SUBSCRIBED_STRIP_ALARMS, CalDavConstants.CS_NAMESPACE); - - /** - */ - public static final DavPropertyName SUBSCRIBED_STRIP_ATTACHMENTS = DavPropertyName.create( - CalDavConstants.PROPERTY_SUBSCRIBED_STRIP_ATTACHMENTS, CalDavConstants.CS_NAMESPACE); - - /** - */ - public static final DavPropertyName SUBSCRIBED_STRIP_TODOS = DavPropertyName.create( - CalDavConstants.PROPERTY_SUBSCRIBED_STRIP_TODOS, CalDavConstants.CS_NAMESPACE); - - /** - */ - public static final DavPropertyName REFRESHRATE = DavPropertyName.create(CalDavConstants.PROPERTY_REFRESHRATE, - CalDavConstants.CS_NAMESPACE); - - /** - */ - public static final DavPropertyName PUSH_TRANSPORTS = DavPropertyName.create( - CalDavConstants.PROPERTY_PUSH_TRANSPORTS, CalDavConstants.CS_NAMESPACE); - - /** - */ - public static final DavPropertyName PUSHKEY = DavPropertyName.create(CalDavConstants.PROPERTY_PUSHKEY, - CalDavConstants.CS_NAMESPACE); - -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/property/CalDavPropertyName.java b/src/main/java/net/fortuna/ical4j/connector/dav/property/CalDavPropertyName.java deleted file mode 100644 index 7dc31966..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/property/CalDavPropertyName.java +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav.property; - -import net.fortuna.ical4j.connector.dav.CalDavConstants; - -import org.apache.jackrabbit.webdav.property.DavPropertyName; - -/** - * $Id$ - * - * Created on 19/11/2008 - * - * @author Ben - * - */ -public class CalDavPropertyName { - - /** - * - */ - public static final DavPropertyName CALENDAR_DESCRIPTION = DavPropertyName.create( - CalDavConstants.PROPERTY_CALENDAR_DESCRIPTION, CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName CALENDAR_TIMEZONE = DavPropertyName.create( - CalDavConstants.PROPERTY_CALENDAR_TIMEZONE, CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName SUPPORTED_CALENDAR_COMPONENT_SET = DavPropertyName.create( - CalDavConstants.PROPERTY_SUPPORTED_CALENDAR_COMPONENT_SET, CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName SUPPORTED_CALENDAR_DATA = DavPropertyName.create( - CalDavConstants.PROPERTY_SUPPORTED_CALENDAR_DATA, CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName MAX_RESOURCE_SIZE = DavPropertyName.create( - CalDavConstants.PROPERTY_MAX_RESOURCE_SIZE, CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName MIN_DATE_TIME = DavPropertyName.create(CalDavConstants.PROPERTY_MIN_DATE_TIME, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName MAX_DATE_TIME = DavPropertyName.create(CalDavConstants.PROPERTY_MAX_DATE_TIME, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName MAX_INSTANCES = DavPropertyName.create(CalDavConstants.PROPERTY_MAX_INSTANCES, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName MAX_ATTENDEES_PER_INSTANCE = DavPropertyName.create( - CalDavConstants.PROPERTY_MAX_ATTENDEES_PER_INSTANCE, CalDavConstants.CALDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName CALENDAR_DATA = DavPropertyName.create(CalDavConstants.PROPERTY_CALENDAR_DATA, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * Property from a draft (draft-desruisseaux-ischedule-01) - */ - public static final DavPropertyName RECIPIENT = DavPropertyName.create(CalDavConstants.PROPERTY_RECIPIENT, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * Property from a draft (draft-desruisseaux-ischedule-01) - */ - public static final DavPropertyName REQUEST_STATUS = DavPropertyName.create( - CalDavConstants.PROPERTY_REQUEST_STATUS, CalDavConstants.CALDAV_NAMESPACE); - - public static final DavPropertyName COMPONENT = DavPropertyName.create(CalDavConstants.PROPERTY_COMPONENT, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * Purpose: Identify the calendars that contribute to the free-busy information for the owner of the scheduling - * https://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04 - * - * THIS PROPERTY WAS REMOVED IN DRAFT 05 AND THE OFFICIAL RFC (6638) - * - */ - public static final DavPropertyName FREE_BUSY_SET = DavPropertyName.create(CalDavConstants.PROPERTY_FREE_BUSY_SET, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * Purpose: Identifies the URL of any WebDAV collections that contain calendar collections owned by the associated - * principal resource. RFC : rfc4791 - */ - public static final DavPropertyName CALENDAR_HOME_SET = DavPropertyName.create(CalDavConstants.PROPERTY_CALENDAR_HOME_SET, - CalDavConstants.CALDAV_NAMESPACE); - - /** - * Identify the calendar addresses of the associated principal resource. - * http://tools.ietf.org/html/rfc6638 - */ - public static final DavPropertyName USER_ADDRESS_SET = DavPropertyName.create( - CalDavConstants.PROPERTY_USER_ADDRESS_SET, CalDavConstants.CALDAV_NAMESPACE); - - /** - * Identifies the calendar user type of the associated principal resource. Its value is the same as the iCalendar "CUTYPE". - * https://tools.ietf.org/html/rfc6638 - */ - public static final DavPropertyName USER_TYPE = DavPropertyName.create( - CalDavConstants.PROPERTY_USER_TYPE, CalDavConstants.CALDAV_NAMESPACE); - - /** - * Identify the URL of the scheduling Inbox collection owned by the associated principal resource. - * http://tools.ietf.org/html/rfc6638 - */ - public static final DavPropertyName SCHEDULE_INBOX_URL = DavPropertyName.create( - CalDavConstants.PROPERTY_SCHEDULE_INBOX_URL, CalDavConstants.CALDAV_NAMESPACE); - - /** - * Identify the URL of the scheduling Outbox collection owned by the associated principal resource. - * http://tools.ietf.org/html/rfc6638 - */ - public static final DavPropertyName SCHEDULE_OUTBOX_URL = DavPropertyName.create( - CalDavConstants.PROPERTY_SCHEDULE_OUTBOX_URL, CalDavConstants.CALDAV_NAMESPACE); - - /** - * Determines whether the calendar object resources in a calendar collection will affect the owner's freebusy. - */ - public static final DavPropertyName SCHEDULE_CALENDAR_TRANSP = DavPropertyName.create( - CalDavConstants.PROPERTY_SCHEDULE_CALENDAR_TRANSP, CalDavConstants.CALDAV_NAMESPACE); - - /** - * Specifies a default calendar for an attendee that will automatically have new scheduling messages deposited into - * it when they arrive. - */ - public static final DavPropertyName SCHEDULE_DEFAULT_CALENDAR_URL = DavPropertyName.create( - CalDavConstants.PROPERTY_SCHEDULE_DEFAULT_CALENDAR_URL, CalDavConstants.CALDAV_NAMESPACE); - -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/property/CardDavPropertyName.java b/src/main/java/net/fortuna/ical4j/connector/dav/property/CardDavPropertyName.java deleted file mode 100644 index bd835e12..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/dav/property/CardDavPropertyName.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.connector.dav.property; - -import net.fortuna.ical4j.connector.dav.CalDavConstants; - -import org.apache.jackrabbit.webdav.property.DavPropertyName; - -/** - * $Id$ - * - * Created on 19/11/2008 - * - * @author Ben - * - */ -public class CardDavPropertyName { - - /** - * - */ - public static final DavPropertyName MAX_RESOURCE_SIZE = DavPropertyName.create( - CalDavConstants.PROPERTY_MAX_RESOURCE_SIZE, CalDavConstants.CARDDAV_NAMESPACE); - - /** - * Purpose: Identifies the URL of any WebDAV collections that contain address book collections owned by the associated - * principal resource. RFC : rfc6352 - */ - public static final DavPropertyName ADDRESSBOOK_HOME_SET = DavPropertyName.create(CalDavConstants.PROPERTY_ADDRESSBOOK_HOME_SET, - CalDavConstants.CARDDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName SUPPORTED_ADDRESS_DATA = DavPropertyName.create( - CalDavConstants.PROPERTY_SUPPORTED_ADDRESS_DATA, CalDavConstants.CARDDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName MAX_IMAGE_SIZE = DavPropertyName.create( - CalDavConstants.PROPERTY_MAX_IMAGE_SIZE, CalDavConstants.CARDDAV_NAMESPACE); - - /** - * - */ - public static final DavPropertyName ADDRESS_DATA = DavPropertyName.create( - CalDavConstants.PROPERTY_ADDRESS_DATA, CalDavConstants.CARDDAV_NAMESPACE); - -} diff --git a/src/main/java/net/fortuna/ical4j/connector/dav/response/PropFindResponseHandler.java b/src/main/java/net/fortuna/ical4j/connector/dav/response/PropFindResponseHandler.java index 1b4643b1..dd1e8dda 100644 --- a/src/main/java/net/fortuna/ical4j/connector/dav/response/PropFindResponseHandler.java +++ b/src/main/java/net/fortuna/ical4j/connector/dav/response/PropFindResponseHandler.java @@ -97,9 +97,11 @@ public Map getCollections(List types) thro DavProperty property = iNames.nextProperty(); if (property != null) { _properties.add(property); - ResourceType resourceType = getResourceType(property); - if (resourceType != null && types.contains(resourceType)) { - isCollection = true; + List resourceTypes = getResourceTypes(property); + for (ResourceType resourceType : resourceTypes) { + if (types.contains(resourceType)) { + isCollection = true; + } } } } @@ -113,7 +115,8 @@ public Map getCollections(List types) thro return collections; } - private ResourceType getResourceType(DavProperty property) { + private List getResourceTypes(DavProperty property) { + List resourceTypes = new ArrayList<>(); if ((DavConstants.PROPERTY_RESOURCETYPE.equals(property.getName().getName())) && (DavConstants.NAMESPACE.equals(property.getName().getNamespace()))) { Object value = property.getValue(); if (value instanceof ArrayList) { @@ -121,13 +124,13 @@ private ResourceType getResourceType(DavProperty property) { if (child instanceof Element) { String nameNode = child.getLocalName(); if (nameNode != null) { - return ResourceType.findByDescription(nameNode); + resourceTypes.add(ResourceType.findByDescription(nameNode)); } } } } } - return null; + return resourceTypes; } public String getDavPropertyUri(DavPropertyName type) throws DavException { diff --git a/src/main/java/net/fortuna/ical4j/connector/local/AbstractLocalObjectStore.java b/src/main/java/net/fortuna/ical4j/connector/local/AbstractLocalObjectStore.java deleted file mode 100644 index 53d61583..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/local/AbstractLocalObjectStore.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.fortuna.ical4j.connector.local; - -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStore; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.model.Calendar; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -public abstract class AbstractLocalObjectStore> implements ObjectStore { - - private final File root; - - AbstractLocalObjectStore(File root) { - if (root.exists() && !root.isDirectory()) { - throw new IllegalArgumentException("Root must be a directory"); - } - this.root = root; - } - - protected File getRoot() { - return root; - } - - @Override - public boolean connect() throws ObjectStoreException { - return false; - } - - @Override - public boolean connect(String username, char[] password) throws ObjectStoreException { - return false; - } - - @Override - public void disconnect() throws ObjectStoreException { - - } - - @Override - public boolean isConnected() { - return false; - } - - @Override - public C addCollection(String id) throws ObjectStoreException { - File collectionDir = new File(root, id); - if ((collectionDir.exists() && !collectionDir.isDirectory()) || - (!collectionDir.exists() && !collectionDir.mkdirs())) { - throw new ObjectStoreException("Unable to initialise collection"); - } - C collection = null; - try { - collection = getCollection(id); - } catch (ObjectNotFoundException e) { - collection = newCollection(id); - } - return collection; - } - - protected abstract C newCollection(String id); - - @Override - public C addCollection(String id, String displayName, String description, String[] supportedComponents, Calendar timezone) throws ObjectStoreException { - C collection = addCollection(id); - try { - collection.setDisplayName(displayName); - collection.setDescription(description); - collection.setSupportedComponents(supportedComponents); - collection.setTimeZone(timezone); - } catch (IOException e) { - throw new ObjectStoreException(e); - } - return collection; - } - - @Override - public C removeCollection(String id) throws ObjectStoreException, ObjectNotFoundException { - return null; - } - - @Override - public C getCollection(String id) throws ObjectStoreException, ObjectNotFoundException { - File collectionDir = new File(root, id); - if (!collectionDir.exists() || !collectionDir.isDirectory()) { - throw new ObjectNotFoundException("Unable to retrieve collection"); - } - return newCollection(id); - } - - @Override - public List getCollections() throws ObjectStoreException, ObjectNotFoundException { - return null; - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/local/LocalCalendarCollection.java b/src/main/java/net/fortuna/ical4j/connector/local/LocalCalendarCollection.java deleted file mode 100644 index ca6655ae..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/local/LocalCalendarCollection.java +++ /dev/null @@ -1,132 +0,0 @@ -package net.fortuna.ical4j.connector.local; - -import net.fortuna.ical4j.connector.CalendarCollection; -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.enums.MediaType; -import net.fortuna.ical4j.data.CalendarOutputter; -import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.model.Calendar; -import net.fortuna.ical4j.model.ConstraintViolationException; -import net.fortuna.ical4j.model.property.Uid; -import net.fortuna.ical4j.util.Calendars; - -import java.io.File; -import java.io.FileFilter; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class LocalCalendarCollection extends AbstractLocalObjectCollection implements CalendarCollection { - - private static MediaType[] SUPPORTED_MEDIA_TYPES = new MediaType[1]; - static { - SUPPORTED_MEDIA_TYPES[0] = MediaType.ICALENDAR_2_0; - } - - public LocalCalendarCollection(File root) { - super(root); - } - - @Override - public MediaType[] getSupportedMediaTypes() { - return SUPPORTED_MEDIA_TYPES; - } - - @Override - public long getMaxResourceSize() { - return 0; - } - - @Override - public String getMinDateTime() { - return null; - } - - @Override - public String getMaxDateTime() { - return null; - } - - @Override - public Integer getMaxInstances() { - return null; - } - - @Override - public Integer getMaxAttendeesPerInstance() { - return null; - } - - @Override - public void addCalendar(Calendar calendar) throws ObjectStoreException, ConstraintViolationException { - Uid uid = Calendars.getUid(calendar); - if (uid == null) { - throw new ConstraintViolationException("A valid UID was not found."); - } - - try { - Calendar existing = getCalendar(uid.getValue()); - - // TODO: potentially merge/replace existing.. - throw new ObjectStoreException("Calendar already exists"); - } catch (ObjectNotFoundException e) { - - } - - try (FileWriter writer = new FileWriter(new File(getRoot(), uid.getValue() + ".ics"))) { - new CalendarOutputter(false).output(calendar, writer); - } catch (IOException e) { - throw new ObjectStoreException("Error writing calendar file", e); - } - } - - @Override - public Calendar getCalendar(String uid) throws ObjectNotFoundException { - try { - return Calendars.load(new File(getRoot(), uid + ".ics").getAbsolutePath()); - } catch (IOException | ParserException e) { - throw new ObjectNotFoundException(String.format("Calendar not found: %s", uid), e); - } - } - - @Override - public Calendar removeCalendar(String uid) throws FailedOperationException, ObjectNotFoundException { - Calendar calendar = getCalendar(uid); - if (!new File(getRoot(), uid + ".ics").delete()) { - throw new FailedOperationException("Unable to delete calendar: " + uid); - } - return calendar; - } - - @Override - public void merge(Calendar calendar) throws FailedOperationException, ObjectStoreException { - } - - @Override - public Calendar export() throws ObjectStoreException { - return null; - } - - @Override - public Calendar[] getComponents() throws ObjectStoreException { - List calendars = new ArrayList<>(); - - try { - for (File file : getRoot().listFiles(new FileFilter() { - @Override - public boolean accept(File pathname) { - return !pathname.isDirectory() && pathname.getName().endsWith(".ics"); - } - })) { - calendars.add(Calendars.load(file.getAbsolutePath())); - } - } catch (IOException | ParserException e) { - throw new ObjectStoreException(e); - } - - return calendars.toArray(new Calendar[calendars.size()]); - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/local/LocalCalendarStore.java b/src/main/java/net/fortuna/ical4j/connector/local/LocalCalendarStore.java deleted file mode 100644 index ff334ca6..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/local/LocalCalendarStore.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.fortuna.ical4j.connector.local; - -import net.fortuna.ical4j.connector.CalendarStore; - -import java.io.File; - -public class LocalCalendarStore extends AbstractLocalObjectStore - implements CalendarStore { - - public LocalCalendarStore(File root) { - super(root); - } - - @Override - protected LocalCalendarCollection newCollection(String id) { - return new LocalCalendarCollection(new File(getRoot(), id)); - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/local/LocalCardCollection.java b/src/main/java/net/fortuna/ical4j/connector/local/LocalCardCollection.java deleted file mode 100644 index bbe58e56..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/local/LocalCardCollection.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.fortuna.ical4j.connector.local; - -import net.fortuna.ical4j.connector.CardCollection; -import net.fortuna.ical4j.connector.FailedOperationException; -import net.fortuna.ical4j.connector.ObjectNotFoundException; -import net.fortuna.ical4j.connector.ObjectStoreException; -import net.fortuna.ical4j.connector.dav.enums.MediaType; -import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.model.ConstraintViolationException; -import net.fortuna.ical4j.vcard.Property; -import net.fortuna.ical4j.vcard.VCard; -import net.fortuna.ical4j.vcard.VCardBuilder; -import net.fortuna.ical4j.vcard.VCardOutputter; -import net.fortuna.ical4j.vcard.property.Uid; - -import java.io.*; -import java.util.ArrayList; -import java.util.List; - -public class LocalCardCollection extends AbstractLocalObjectCollection implements CardCollection { - - private static MediaType[] SUPPORTED_MEDIA_TYPES = new MediaType[1]; - static { - SUPPORTED_MEDIA_TYPES[0] = MediaType.VCARD_4_0; - } - - public LocalCardCollection(File root) { - super(root); - } - - @Override - public void addCard(VCard card) throws ObjectStoreException, ConstraintViolationException { - Uid uid = card.getProperty(Property.Id.UID); - if (uid == null) { - throw new ConstraintViolationException("A valid UID was not found."); - } - - try { - VCard existing = getCard(uid.getValue()); - - // TODO: potentially merge/replace existing.. - throw new ObjectStoreException("Card already exists"); - } catch (ObjectNotFoundException e) { - - } - - try (FileWriter writer = new FileWriter(new File(getRoot(), uid.getValue() + ".vcf"))) { - new VCardOutputter(false).output(card, writer); - } catch (IOException e) { - throw new ObjectStoreException("Error writing card file", e); - } - } - - public VCard getCard(String uid) throws ObjectNotFoundException { - try { - return new VCardBuilder(new FileInputStream(new File(getRoot(), uid + ".vcf"))).build(); - } catch (IOException | ParserException e) { - throw new ObjectNotFoundException(String.format("Card not found: %s", uid), e); - } - } - - @Override - public VCard removeCard(String uid) throws ObjectNotFoundException, FailedOperationException { - VCard card = getCard(uid); - if (!new File(getRoot(), uid + ".vcf").delete()) { - throw new FailedOperationException("Unable to delete card: " + uid); - } - return card; - } - - @Override - public VCard[] getComponents() throws ObjectStoreException { - List cards = new ArrayList<>(); - - try { - for (File file : getRoot().listFiles(new FileFilter() { - @Override - public boolean accept(File pathname) { - return !pathname.isDirectory() && pathname.getName().endsWith(".vcf"); - } - })) { - VCardBuilder builder = new VCardBuilder(new FileInputStream(file)); - cards.add(builder.build()); - } - } catch (IOException | ParserException e) { - throw new ObjectStoreException(e); - } - return cards.toArray(new VCard[cards.size()]); - } -} diff --git a/src/main/java/net/fortuna/ical4j/connector/local/LocalCardStore.java b/src/main/java/net/fortuna/ical4j/connector/local/LocalCardStore.java deleted file mode 100644 index 928bd4d0..00000000 --- a/src/main/java/net/fortuna/ical4j/connector/local/LocalCardStore.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.fortuna.ical4j.connector.local; - -import net.fortuna.ical4j.connector.CardStore; - -import java.io.File; - -public class LocalCardStore extends AbstractLocalObjectStore - implements CardStore { - - public LocalCardStore(File root) { - super(root); - } - - @Override - protected LocalCardCollection newCollection(String id) { - return new LocalCardCollection(new File(getRoot(), id)); - } -} diff --git a/src/test/groovy/net/fortuna/ical4j/connector/dav/DavClientTest.groovy b/src/test/groovy/net/fortuna/ical4j/connector/dav/DavClientTest.groovy deleted file mode 100644 index e538f128..00000000 --- a/src/test/groovy/net/fortuna/ical4j/connector/dav/DavClientTest.groovy +++ /dev/null @@ -1,22 +0,0 @@ -package net.fortuna.ical4j.connector.dav - -import spock.lang.Ignore -import spock.lang.Specification - -@Ignore -class DavClientTest extends Specification { - - def 'assert preemptive auth configuration'() { - given: 'a dav client factory configured for preemptive auth' - DavClientFactory clientFactory = [true] - - when: 'a new client instance is created' - DavClient client = clientFactory.newInstance(URI.create('http://dav.example.com').toURL(), '', ''); - - and: 'a session is initated' - client.begin() - - then: 'the client is configured for preemptive auth' - client.httpClient.params.authenticationPreemptive == true - } -} diff --git a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCalendarCollectionTest.groovy b/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCalendarCollectionTest.groovy deleted file mode 100644 index 832715b6..00000000 --- a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCalendarCollectionTest.groovy +++ /dev/null @@ -1,72 +0,0 @@ -package net.fortuna.ical4j.connector.local - -import net.fortuna.ical4j.model.Calendar -import net.fortuna.ical4j.model.Component -import net.fortuna.ical4j.model.ContentBuilder -import net.fortuna.ical4j.model.Property -import net.fortuna.ical4j.util.Calendars -import net.fortuna.ical4j.util.RandomUidGenerator -import spock.lang.Specification - -class LocalCalendarCollectionTest extends Specification { - - def 'test add calendar to collection'() { - given: 'a local calendar collection' - LocalCalendarStore calendarStore = [new File('build', 'local')] - LocalCalendarCollection collection = calendarStore.addCollection('public_holidays') - - and: 'a calendar object' - Calendar calendar = new ContentBuilder().with { - calendar { - prodid '-//Ben Fortuna//iCal4j 1.0//EN' - version '2.0' - vevent { - uid new RandomUidGenerator().generateUid() - dtstamp() - dtstart('20090810', parameters: parameters { value 'DATE' }) - action 'DISPLAY' - attach('http://example.com/attachment', parameters: parameters() { value 'URI' }) - } - } - } - - when: 'the new calendar is added to the collection' - collection.addCalendar(calendar) - - then: 'a new calendar file is created' - new File('build/local/public_holidays', - "${calendar.getComponent(Component.VEVENT).getProperty(Property.UID).getValue()}.ics").exists() - } - - def 'test remove calendar from collection'() { - given: 'a local calendar collection' - LocalCalendarStore calendarStore = [new File('build', 'local')] - LocalCalendarCollection collection = calendarStore.addCollection('public_holidays') - - and: 'a calendar object that is added to the collection' - Calendar calendar = new ContentBuilder().with { - calendar { - prodid '-//Ben Fortuna//iCal4j 1.0//EN' - version '2.0' - vevent { - uid new RandomUidGenerator().generateUid() - dtstamp() - dtstart('20090810', parameters: parameters { value 'DATE' }) - action 'DISPLAY' - attach('http://example.com/attachment', parameters: parameters() { value 'URI' }) - } - } - } - collection.addCalendar(calendar) - - when: 'the calendar is removed from the collection' - def removed = collection.removeCalendar(Calendars.getUid(calendar).value) - - then: 'the exsiting calendar file is deleted' - !new File('build/local/public_holidays', - "${calendar.getComponent(Component.VEVENT).getProperty(Property.UID).getValue()}.ics").exists() - - and: 'removed calendar is identical to added' - removed == calendar - } -} diff --git a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCalendarStoreTest.groovy b/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCalendarStoreTest.groovy deleted file mode 100644 index f891cc12..00000000 --- a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCalendarStoreTest.groovy +++ /dev/null @@ -1,44 +0,0 @@ -package net.fortuna.ical4j.connector.local - -import net.fortuna.ical4j.model.Calendar -import net.fortuna.ical4j.model.Component -import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory -import net.fortuna.ical4j.util.Calendars -import spock.lang.Specification - -class LocalCalendarStoreTest extends Specification { - - def 'test create collection'() { - given: 'a new local calendar store' - LocalCalendarStore calendarStore = [new File('build', 'local')] - - when: 'a new collection is added' - LocalCalendarCollection collection = calendarStore.addCollection('public_holidays') - - then: 'a local collection directory is added' - new File('build/local', 'public_holidays').exists() - } - - def 'test create and initialise collection'() { - given: 'a new local calendar store' - LocalCalendarStore calendarStore = [new File('build', 'local')] - - and: 'a timezone calendar' - Calendar timezone = Calendars.wrap(new DefaultTimeZoneRegistryFactory().createRegistry() - .getTimeZone('Australia/Melbourne').getVTimeZone()) - - when: 'a new collection is added' - LocalCalendarCollection collection = calendarStore.addCollection('public_holidays', 'Public Holidays', - 'Victorian public holidays', [Component.VEVENT] as String[], timezone) - - then: 'a local collection directory is added' - new File('build/local', 'public_holidays').exists() - - and: 'the collection properties are saved' - collection.displayName == 'Public Holidays' - collection.description == 'Victorian public holidays' - collection.supportedComponentTypes == [Component.VEVENT] as String[] - collection.timeZone == timezone - - } -} diff --git a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCardCollectionTest.groovy b/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCardCollectionTest.groovy deleted file mode 100644 index 5fd20fe6..00000000 --- a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCardCollectionTest.groovy +++ /dev/null @@ -1,60 +0,0 @@ -package net.fortuna.ical4j.connector.local - -import net.fortuna.ical4j.vcard.ContentBuilder -import net.fortuna.ical4j.vcard.Property -import spock.lang.Specification - -class LocalCardCollectionTest extends Specification { - - def 'test add card to collection'() { - given: 'a local card collection' - LocalCardStore cardStore = [new File('build', 'local')] - LocalCardCollection collection = cardStore.addCollection('contacts') - - and: 'a card object' - def card = new ContentBuilder().vcard() { - version '4.0' - uid UUID.randomUUID().toString() - fn 'test' - n('example') { - value 'text' - } - photo(value: 'http://example.com', parameters: [value('uri')]) - } - - when: 'the new card is added to the collection' - collection.addCard(card) - - then: 'a new card file is created' - new File('build/local/contacts', - "${card.getProperty(Property.Id.UID).getValue()}.vcf").exists() - } - - def 'test remove card from collection'() { - given: 'a local card collection' - LocalCardStore cardStore = [new File('build', 'local')] - LocalCardCollection collection = cardStore.addCollection('contacts') - - and: 'a card object added' - def card = new ContentBuilder().vcard() { - version '4.0' - uid UUID.randomUUID().toString() - fn 'test' - n('example') { - value 'text' - } - photo(value: 'http://example.com', parameters: [value('uri')]) - } - collection.addCard(card) - - when: 'the card is removed' - def removed = collection.removeCard(card.getProperty(Property.Id.UID).value) - - then: 'the existing card file is deleted' - !new File('build/local/contacts', - "${card.getProperty(Property.Id.UID).getValue()}.vcf").exists() - - and: 'removed card is identical to added' - removed == card - } -} diff --git a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCardStoreTest.groovy b/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCardStoreTest.groovy deleted file mode 100644 index 9173f433..00000000 --- a/src/test/groovy/net/fortuna/ical4j/connector/local/LocalCardStoreTest.groovy +++ /dev/null @@ -1,42 +0,0 @@ -package net.fortuna.ical4j.connector.local - -import net.fortuna.ical4j.model.Calendar -import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory -import net.fortuna.ical4j.util.Calendars -import spock.lang.Specification - -class LocalCardStoreTest extends Specification { - - def 'test create collection'() { - given: 'a new local card store' - LocalCardStore cardStore = [new File('build', 'local')] - - when: 'a new collection is added' - LocalCardCollection collection = cardStore.addCollection('contacts') - - then: 'a local collection directory is added' - new File('build/local', 'contacts').exists() - } - - def 'test create and initialise collection'() { - given: 'a new local card store' - LocalCardStore cardStore = [new File('build', 'local')] - - and: 'a timezone card' - Calendar timezone = Calendars.wrap(new DefaultTimeZoneRegistryFactory().createRegistry() - .getTimeZone('Australia/Melbourne').getVTimeZone()) - - when: 'a new collection is added' - LocalCardCollection collection = cardStore.addCollection('contacts', 'Contacts', - 'Personal Contacts', ['VCARD'] as String[], timezone) - - then: 'a local collection directory is added' - new File('build/local', 'contacts').exists() - - and: 'the collection properties are saved' - collection.displayName == 'Contacts' - collection.description == 'Personal Contacts' - collection.supportedComponentTypes == ['VCARD'] as String[] - collection.timeZone == timezone - } -}