commit 493961ad0b011dbd052094eabf76bec09e4f184c Author: Alfredo Oliviero Date: Tue Feb 6 16:26:56 2024 +0100 first commit. smartgear service, keycloack client prototipe diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..413a1b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,171 @@ +# originale +target +.classpath +.visual +.project +.settings +/**/.DS_Store + +# Created by https://www.toptal.com/developers/gitignore/api/java,macos,linux,visualstudiocode,eclipse +# Edit at https://www.toptal.com/developers/gitignore?templates=java,macos,linux,visualstudiocode,eclipse + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +### Eclipse Patch ### +# Spring Boot Tooling +.sts4-cache/ + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/java,macos,linux,visualstudiocode,eclipse diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d53ecaf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b140c02 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# Changelog for Identity Manager Service + + +## [v1.0.0-SNAPSHOT] + +- First Version + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d601f78 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM smartgears-distribution:4.0.0-java11-tomcat9 +ARG REPOUSER=admin +ARG REPOPWD=admin +COPY ./target/identity-manager.war /tomcat/webapps/ +# COPY ./docker/storagehub.xml /tomcat/conf/Catalina/localhost/ +COPY ./docker/logback.xml /etc/ +COPY ./docker/container.ini /etc/ +RUN unzip /tomcat/webapps/identity-manager.war -d /tomcat/webapps/identity-manager +RUN rm /tomcat/webapps/identity-manager.war +# COPY ./docker/storage-settings.properties /tomcat/webapps/identity-manager/WEB-INF/classes/ +RUN sed -i "s/{{adminId}}/$REPOUSER/g; s/{{adminPwd}}/$REPOPWD/g" /tomcat/webapps/storagehub/WEB-INF/web.xml diff --git a/FUNDING.md b/FUNDING.md new file mode 100644 index 0000000..9e48b94 --- /dev/null +++ b/FUNDING.md @@ -0,0 +1,26 @@ +# Acknowledgments + +The projects leading to this software have received funding from a series of European Union programmes including: + +- the Sixth Framework Programme for Research and Technological Development + - [DILIGENT](https://cordis.europa.eu/project/id/004260) (grant no. 004260). +- the Seventh Framework Programme for research, technological development and demonstration + - [D4Science](https://cordis.europa.eu/project/id/212488) (grant no. 212488); + - [D4Science-II](https://cordis.europa.eu/project/id/239019) (grant no.239019); + - [ENVRI](https://cordis.europa.eu/project/id/283465) (grant no. 283465); + - [iMarine](https://cordis.europa.eu/project/id/283644) (grant no. 283644); + - [EUBrazilOpenBio](https://cordis.europa.eu/project/id/288754) (grant no. 288754). +- the H2020 research and innovation programme + - [SoBigData](https://cordis.europa.eu/project/id/654024) (grant no. 654024); + - [PARTHENOS](https://cordis.europa.eu/project/id/654119) (grant no. 654119); + - [EGI-Engage](https://cordis.europa.eu/project/id/654142) (grant no. 654142); + - [ENVRI PLUS](https://cordis.europa.eu/project/id/654182) (grant no. 654182); + - [BlueBRIDGE](https://cordis.europa.eu/project/id/675680) (grant no. 675680); + - [PerformFISH](https://cordis.europa.eu/project/id/727610) (grant no. 727610); + - [AGINFRA PLUS](https://cordis.europa.eu/project/id/731001) (grant no. 731001); + - [DESIRA](https://cordis.europa.eu/project/id/818194) (grant no. 818194); + - [ARIADNEplus](https://cordis.europa.eu/project/id/823914) (grant no. 823914); + - [RISIS 2](https://cordis.europa.eu/project/id/824091) (grant no. 824091); + - [EOSC-Pillar](https://cordis.europa.eu/project/id/857650) (grant no. 857650); + - [Blue Cloud](https://cordis.europa.eu/project/id/862409) (grant no. 862409); + - [SoBigData-PlusPlus](https://cordis.europa.eu/project/id/871042) (grant no. 871042); diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3af0507 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,312 @@ +# European Union Public Licence V. 1.1 + + +EUPL © the European Community 2007 + + +This European Union Public Licence (the “EUPL”) applies to the Work or Software +(as defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Original Work is provided under the terms of this Licence when the Licensor +(as defined below) has placed the following notice immediately following the +copyright notice for the Original Work: + +Licensed under the EUPL V.1.1 + +or has expressed by any other mean his willingness to license under the EUPL. + + + +## 1. Definitions + +In this Licence, the following terms have the following meaning: + +- The Licence: this Licence. + +- The Original Work or the Software: the software distributed and/or + communicated by the Licensor under this Licence, available as Source Code and + also as Executable Code as the case may be. + +- Derivative Works: the works or software that could be created by the Licensee, + based upon the Original Work or modifications thereof. This Licence does not + define the extent of modification or dependence on the Original Work required + in order to classify a work as a Derivative Work; this extent is determined by + copyright law applicable in the country mentioned in Article 15. + +- The Work: the Original Work and/or its Derivative Works. + +- The Source Code: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- The Executable Code: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- The Licensor: the natural or legal person that distributes and/or communicates + the Work under the Licence. + +- Contributor(s): any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- The Licensee or “You”: any natural or legal person who makes any usage of the + Software under the terms of the Licence. + +- Distribution and/or Communication: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, on-line or off-line, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + + + +## 2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a world-wide, royalty-free, non-exclusive, +sub-licensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, reproduce the Work, modify +- the Original Work, and make Derivative Works based upon the Work, communicate +- to the public, including the right to make available or display the Work or +- copies thereof to the public and perform publicly, as the case may be, the +- Work, distribute the Work or copies thereof, lend and rent the Work or copies +- thereof, sub-license rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + + + +## 3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute and/or communicate the Work. + + + +## 4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Original Work or Software, of the exhaustion of those rights or of other +applicable limitations thereto. + + + +## 5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: the Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes and/or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes and/or communicates copies of the +Original Works or Derivative Works based upon the Original Work, this +Distribution and/or Communication will be done under the terms of this Licence +or of a later version of this Licence unless the Original Work is expressly +distributed only under this version of the Licence. The Licensee (becoming +Licensor) cannot offer or impose any additional terms or conditions on the Work +or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes and/or Communicates Derivative +Works or copies thereof based upon both the Original Work and another work +licensed under a Compatible Licence, this Distribution and/or Communication can +be done under the terms of this Compatible Licence. For the sake of this clause, +“Compatible Licence” refers to the licences listed in the appendix attached to +this Licence. Should the Licensee’s obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing and/or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available for +as long as the Licensee continues to distribute and/or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + + + +## 6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + + + +## 7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +contributors. It is not a finished work and may therefore contain defects or +“bugs” inherent to this type of software development. + +For the above reason, the Work is provided under the Licence on an “as is” basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + + + +## 8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + + + +## 9. Additional agreements + +While distributing the Original Work or Derivative Works, You may choose to +conclude an additional agreement to offer, and charge a fee for, acceptance of +support, warranty, indemnity, or other liability obligations and/or services +consistent with this Licence. However, in accepting such obligations, You may +act only on your own behalf and on your sole responsibility, not on behalf of +the original Licensor or any other Contributor, and only if You agree to +indemnify, defend, and hold each Contributor harmless for any liability incurred +by, or claims asserted against such Contributor by the fact You have accepted +any such warranty or additional liability. + + + +## 10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon “I agree” +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution and/or Communication by You of the Work or copies thereof. + + + +## 11. Information to the public + +In case of any Distribution and/or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + + + +## 12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + + + +## 13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work licensed hereunder. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed and/or reformed so as necessary to make +it valid and enforceable. + +The European Commission may publish other linguistic versions and/or new +versions of this Licence, so far this is required and reasonable, without +reducing the scope of the rights granted by the Licence. New versions of the +Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + + + +## 14. Jurisdiction + +Any litigation resulting from the interpretation of this License, arising +between the European Commission, as a Licensor, and any Licensee, will be +subject to the jurisdiction of the Court of Justice of the European Communities, +as laid down in article 238 of the Treaty establishing the European Community. + +Any litigation arising between Parties, other than the European Commission, and +resulting from the interpretation of this License, will be subject to the +exclusive jurisdiction of the competent court where the Licensor resides or +conducts its primary business. + + + +## 15. Applicable Law + +This Licence shall be governed by the law of the European Union country where +the Licensor resides or has his registered office. + +This licence shall be governed by the Belgian law if: + +- a litigation arises between the European Commission, as a Licensor, and any +- Licensee; the Licensor, other than the European Commission, has no residence +- or registered office inside a European Union country. + + + +## Appendix + + + +“Compatible Licences” according to article 5 EUPL are: + + +- GNU General Public License (GNU GPL) v. 2 + +- Open Software License (OSL) v. 2.1, v. 3.0 + +- Common Public License v. 1.0 + +- Eclipse Public License v. 1.0 + +- Cecill v. 2.0 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e5b2e3a --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Identity Manager Service + +This service allows any client to publish on the gCube Catalogue. + +## Built With + +* [OpenJDK](https://openjdk.java.net/) - The JDK used +* [Maven](https://maven.apache.org/) - Dependency Management + +## Documentation + +[Identity Manager Service](https://wiki.gcube-system.org/gcube/SmartGears) + +## Change log + +See [CHANGELOG.md](CHANGELOG.md). + +## Authors + +* **Alfredo Oliviero** [ISTI-CNR Infrascience Group](http://nemis.isti.cnr.it/groups/infrascience) +* **Luca Frosini** ([ORCID](https://orcid.org/0000-0003-3183-2291)) - [ISTI-CNR Infrascience Group](http://nemis.isti.cnr.it/groups/infrascience) + +## How to Cite this Software + +Tell people how to cite this software. +* Cite an associated paper? +* Use a specific BibTeX entry for the software? + + @software{gcat, + author = {{Alfredo Oliviero}}, + title = {Identity Manager Service}, + abstract = {This is an Identity Manager smargears service}, + url = {doi Zenodo URL} + keywords = {D4Science, gCube} + } + +## License + +This project is licensed under the EUPL V.1.1 License - see the [LICENSE.md](LICENSE.md) file for details. + + +## About the gCube Framework +This software is part of the [gCubeFramework](https://www.gcube-system.org/ "gCubeFramework"): an +open-source software toolkit used for building and operating Hybrid Data +Infrastructures enabling the dynamic deployment of Virtual Research Environments +by favouring the realisation of reuse oriented policies. + +The projects leading to this software have received funding from a series of European Union programmes see [FUNDING.md](FUNDING.md) + diff --git a/api.md b/api.md new file mode 100644 index 0000000..76dfd02 --- /dev/null +++ b/api.md @@ -0,0 +1,21 @@ + + +/2/users/get-profile // profilo utente corrente +/2/users/get-email // utente corrente +/2/users/get-fullname // utente corrente + +/2/users/get-all-usernames +/2/users/get-all-fullnames-and-usernames +/2/users/get-usernames-by-role +/2/users/user-exists + +// attenzione al risultato. vedere in seguito +/2/users/get-oauth-profile + +// eventualemente in seguito. da approfondire +/2/users/get-custom-attribute +/2/users/get-usernames-by-global-role + +rif. https://api.dev.d4science.org/social-networking-library-ws/api-docs/index.html + +implementazione social: https://code-repo.d4science.org/gCubeSystem/social-networking-library-ws/src/branch/master/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Users.java diff --git a/appunti.md b/appunti.md new file mode 100644 index 0000000..84867e5 --- /dev/null +++ b/appunti.md @@ -0,0 +1,23 @@ +eseguendo ```mvn install``` su gcat, ricevo questo errore + +[ERROR] Failed to execute goal org.codehaus.gmaven:groovy-maven-plugin:2.1.1:execute (default) on project gcat: Execution default of goal org.codehaus.gmaven:groovy-maven-plugin:2.1.1:execute failed: startup failed: +[ERROR] script1.groovy: 1: expecting EOF, found 'matcher' @ line 1, column 100. +[ERROR] ANGELOG.md").getText('UTF-8') matcher = +[ERROR] ^ +[ERROR] +[ERROR] 1 error +[ERROR] + + +https://maven.d4science.org/nexus/content/repositories/gcube-externals/org/gcube/tools/maven-parent/1.1.0/maven-parent-1.1.0.pom + + + +cd ~/.m2/repository +grep -R "getText('UTF-8') matcher" . + + + def fileContents = new File("${project.basedir}/CHANGELOG.md").getText('UTF-8') matcher = (fileContents =~ /(?s).\[v$project.version\].*?/) if (!matcher.find()) { throw new IllegalArgumentException("Tag [v$project.version] not found in ${project.basedir}/CHANGELOG.md") } assert matcher[0][1]: "Tag [v$project.version] not found in ${project.basedir}/CHANGELOG.md" + + + diff --git a/docker/container.ini b/docker/container.ini new file mode 100644 index 0000000..4de8496 --- /dev/null +++ b/docker/container.ini @@ -0,0 +1,25 @@ +[node] +mode = online +hostname = alfredo-idm-service-dev +protocol= http +port = 8080 +infrastructure = gcube +authorizeChildrenContext = true +publicationFrequencyInSeconds = 60 + +[properties] +SmartGearsDistribution = 4.0.0-SNAPSHOT +SmartGearsDistributionBundle = UnBundled + +[site] +country = it +location = pisa + +[authorization] +factory = org.gcube.smartgears.security.defaults.DefaultAuthorizationProviderFactory +factory.endpoint = https://accounts.dev.d4science.org/auth/realms/d4science/protocol/openid-connect/token +credentials.class = org.gcube.smartgears.security.SimpleCredentials +; credentials.clientID = node-whn-test-uno-d-d4s.d4science.org +; credentials.secret = 979bd3bc-5cc4-11ec-bf63-0242ac130002 +credentials.clientID = alfredo-idm-service-dev +credentials.secret = 979bd3bc-5cc4-11ec-bf63-0242ac130002 \ No newline at end of file diff --git a/docker/logback.xml b/docker/logback.xml new file mode 100644 index 0000000..906b20e --- /dev/null +++ b/docker/logback.xml @@ -0,0 +1,25 @@ + + + + Ï + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..ba65b13 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..cd70927 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,54 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'gCube Catalogue (gCat) Service' +copyright = '2022, Luca Frosini (ISTI-CNR)' +author = 'Luca Frosini (ISTI-CNR)' + +# The full version, including alpha/beta/rc tags +release = '2.5.1' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinxdoc' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0b79774 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,484 @@ +--- +title: Welcome to gCube Catalogue Service (aka gCat) documentation +--- + +gCat is a RESTful application which exposes operations via REST-API. + +See the available [REST-API docs](../api-docs/index.html). + +Base URL +======== + +In the production environment, its current value is + + +Authorization +============= + +D4Science adopts state-of-the-art industry standards for authentication +and authorization. Specifically, the implementation fully adopts [OIDC +(OpenID Connect)](https://openid.net/connect) for authentication and UMA +2 (User-Managed Authorization) for authorization flows. [JSON Web Token +(JWT) Access token](https://jwt.io/) are used for both authentication +and authorization. + +Obtain your Bearer token here: + + +Service +======= + +You can call the methods of the Web Service by writing your own REST +client application or using existing REST client plugins. + +HTTP Statuses +------------- + +Any successful operation returns *200 OK* HTTP status code. The create +operation returns *201 Created*. Any Background operation returns *202 +Accepted*. Any operation which does not provide any content return *204 +No Content*. + +The most common error status a client can obtain are: + +- **400 Bad Request** used to indicate a clients error + ; +- **401 Unauthorized** used to indicate that the client does not + provide the authorization token in the HTTP Header or the client has + not enough right to perform such request + ; +- **404 Not Found** used to indicate that the requested instance does + not exist ; +- **405 Method Not Allowed** the used HTTP method is not supported for + the requested URL + . The response + contains the *Allow* HTTP Header indicating the supported HTTP + method for such URL + ; +- **409 Conflict** the request could not be completed due to a + conflict with the current state of the target resource (e.g. the + name of the resource already exists) + ; +- **500 Internal Server Error** indicate a server failure + . + +You can find a complete list of HTTP Status at + + +If you get a *500 Internal Server Error*, please report it in the [gCube +ticketing system](https://support.d4science.org). + +Please use this checklist before reporting an error: + +- Replicate the request; +- The failure could be temporal due to network error, server issue and + many other temporal issues. For this reason, please retry the + request after a certain amount of time before reporting the issue; +- indicate how to replicate the error; +- indicate the time when the error occurred (this simplifies + identifying the issue). + +HTTP Methods +------------ + +gCat is a pure RESTful service. It uses standard HTTP Methods to perform +listing of collections and CRUD (Create Read Update Delete) operations +on instances. + +:::{table} Supported operations +:align: center +:widths: grid + + +| Operation | HTTP Method | URL | Success HTTP Status | Safe | Idempotent | +|-----------|-------------|-----|---------------------|------|------------| +| **Supported
HTTP Methods** | OPTIONS | /{COLLECTION} | 204 No Content | Y | Y | +| **List** | GET | /{COLLECTION} | 200 OK | Y | Y | +| **Count** | GET | /{COLLECTION}?count=true | 200 OK | Y | Y | +| **Exists** | HEAD | /{COLLECTION} | 204 No Content | Y | Y | +| **Create** | POST | /{COLLECTION} | 201 Created | N | N | +| **Supported
HTTP Methods** | OPTIONS | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | Y | Y | +| **Exist** | HEAD | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | Y | Y | +| **Read** | GET | /{COLLECTION}/{INSTANCE_ID} | 200 OK | Y | Y | +| **Update** | PUT | /{COLLECTION}/{INSTANCE_ID} | 200 OK | N | Y | +| **Patch** | PATCH | /{COLLECTION}/{INSTANCE_ID} | 200 OK | N | Y | +| **Delete** | DELETE | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | N | N | +| **Purge** | PURGE | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | N | N | +| **Purge** | DELETE | /{COLLECTION}/{INSTANCE_ID}?purge=true | 204 No Content | N | N | + + +### About URL + +The presented URL uses the following convention: + +- **{COLLECTION}** is the plural name of the entity type; +- **{INSTANCE\_ID}** is an identification that enables univocally + identifying the instance in the collection. + +### About Safety and Idempotency properties + +- A method is *Safe* if it does not produce any side effects. \"This + does not prevent an implementation from including behaviour that is + potentially harmful, that is not entirely read-only, or that causes + side effects while invoking a safe method\" + ; +- A method is *Idempotent* if the same operation repeated multiple + times has the same side effect than using it one time. \"repeating + the request will have the same intended effect, even if the original + request succeeded, though the response might differ\" + . + +You can find more information about HTTP Methods at + + +### Uncommon HTTP Methods + +- PATCH method allows to perform a differential update (i.e. an update + which provides only the differences and not the whole new + representation); +- PURGE method is not a standard but is widely used in service which + requires this action (e.g. + [Varnish](https://varnish-cache.org/docs/3.0/tutorial/purging.html), + [Squid](https://wiki.squid-cache.org/SquidFaq/OperatingSquid#How_can_I_purge_an_object_from_my_cache.3F)). + gCat provides support for this method, but to support a wider range + of clients, it also provides the Purge action via *DELETE* with the + additional get parameter `purge=true`. + +Content-Type +------------ + +Any request must contain an indication of the interesting content type. + +The client must specify the **Accept** HTTP Header for any operation +returning a result. + +``` {.rest} +Accept: application/json +``` + +For any operation sending content to the service, it is necessary to +specify the **Content-Type** HTTP Header. + +``` {.rest} +Content-Type: application/json +``` + +The service accepts and returns only JSON objects. + +[Profile Collection](../api-docs/resource\_Profile.html) instead can be +manipulated in XML only. + +Collections +----------- + +The following collections are available to any user. Catalogue-Editor or +above can invoke Non-safe methods only. + +- [Item Collection](../api-docs/resource_Item.html); + - [Resource Collection](../api-docs/resource_Resource.html); +- [Profile Collection](../api-docs/resource_Profile.html); +- [Namespace Collection](../api-docs/resource_Namespace.html); +- [License Collection](../api-docs/resource_License.html); +- [Trash Collection](../api-docs/resource_Trash.html); + +The following collections are available for Catalogue-Admins or above +only: + +- [Group Collection](../api-docs/resource_Group.html); +- [Organization Collection](../api-docs/resource_Organization.html); +- [User Collection](../api-docs/resource_User.html); +- [Configuration Collection](../api-docs/resource_Configuration.html). + +An overview of the available collections is available at +[../api-docs/index.html](../api-docs/index.html); + +Roles +----- + +Any user has one or more roles in the catalogue. Only the VRE Manager +can assign roles to VRE users. + +The catalogue uses the following hierarchic roles: + +**Catalogue-Member**: + +: A user with such a role is mainly capable of listing and reading + items; + +**Catalogue-Editor**: + +: A user with such a role is capable of managing the items he/she + creates and capable of using other safe APIs; + +**Catalogue-Admin**: + +: A user with such a role is capable of administrating many aspects of + the catalogue; + +**Catalogue-Manager**: + +: A user with such a role can use all the APIs exposed by the service + except item moderation APIs (e.g. approve, reject, \...). + +Another role that is not in the role hierarchy: + +**Catalogue-Moderator**: + +: A user with such a role is capable of invoking the item moderation + APIs. + + ::: {.tip} + ::: {.title} + Tip + ::: + ::: + + Please note that not all catalogues are moderated. + +Moderated Catalogues +==================== + +Any catalogues can be declared as moderated. This means that, a +**Catalogue-Moderator** must approve any submitted items to make them +available to the other users of the catalogue. + +In a moderated catalogue, an item can be in the following states: + +**pending**: + +: The item published by any allowed author (a Catalogue-Editor or + above) but not available to the other users of the catalogue. A + Catalogue-Moderator has to approve or reject it; + +**approved**: + +: A Catalogue-Moderator has approved the item published by any allowed + users; + +**rejected**: + +: A Catalogue-Moderator has rejected the item published by any allowed + users. + +The following are the moderation operations that an allowed user can +perform on an item. To present the moderation operations, we use the +following convention: + +> `initial_state` \-\--**operation** (*User/Role performing the +> operation*)\-\--\> `final_state` + +`initial_state` can be `none`, meaning the item does not exist. + +The following are the allowed moderation operation on an item + +> `none` \-\--**create** (*Author*)\-\--\> `pending` +> +> `pending` \-\--**reject** (*Catalogue-Moderator*)\-\--\> `rejected` +> +> `pending` \-\--**approve** (*Catalogue-Moderator*)\-\--\> `approved` +> +> `rejected` \-\--**update** (*Author*)\-\--\> `pending` +> +> `approved` \-\--**update** (*Author*)\-\--\> `pending` + +Please check the table below whcih summarise the item collection +operation and the allowed users/roles. + +In a moderated catalogue, both the Catalogue-Moderators and the item +author can send messages to discuss the approval process of the item. +The messages are related to a specific item. Any Catalogue-Moderators +receive a message sent by an Author. The author receives a message sent +by a Catalogue-Moderator as well as the other Catalogue-Moderators (if +any). + +Messages can be sent both with an action which changes the status of the +item or as explicit action which does not change the status of the item: + +> `pending` \-\--**message** (*Author OR Catalogue-Moderator*)\-\--\> +> `pending` +> +> `rejected` \-\--**message** (*Author OR Catalogue-Moderator*)\-\--\> +> `rejected` +> +> `approved` \-\--**message** (*Author OR Catalogue-Moderator*)\-\--\> +> `approved` + +The following table summarize the allowed/forbidden operations depending +on: the role of the user and the state of the item. + +The Moderation process has associated notification to authors and +Catalogue-Moderators. Please note that the user who has acted is not +self-notified, e.g. approve operation made by a Catalogue-Moderator +notifies the item author and the other Catalogue-Moderators of the VRE. + +The following table summarises the addressee of the notification for any +action. + +Java Client +=========== + +We provide the following Java Client out-of-the-box. + +> ::: {.tip} +> ::: {.title} +> Tip +> ::: +> +> If you\'re coding in Java, it is recommended that you use this Java +> Client. +> ::: + +**Maven Coordinates** + +``` {.xml} +org.gcube.data-catalogue +gcat-client +[2.2.0, 3.0.0-SNAPSHOT) +``` + +**Methods Result** + +The service exposes [its methods](../api-docs/index.html) using a +standard naming approach. Moreover, they accept (in the case of HTTP +POST/PUT methods) JSON objects. + +> ::: {.important} +> ::: {.title} +> Important +> ::: +> +> The result of all methods is always a JSON object as per below: +> ::: + +``` {.javascript} +{ + "rating": 0.0, + "license_title": "Creative Commons Attribution Share-Alike 4.0", + "maintainer": "Frosini Luca", + "relationships_as_object": [], + "private": false, + "maintainer_email": "luca.frosini@isti.cnr.it", + "num_tags": 1, + "id": "17051d86-c127-4928-9296-d3d7590161fe", + "metadata_created": "2022-10-17T12:45:53.118318", + "metadata_modified": "2022-10-18T10:30:03.362756", + "author": "Frosini Luca", + "author_email": "luca.frosini@isti.cnr.it", + "state": "active", + "version": null, + "creator_user_id": "f1b0265c-9983-4f97-a7b6-be3cc0544b27", + "type": "dataset", + "resources": [], + "num_resources": 0, + "tags": [ + { + "vocabulary_id": null, + "state": "active", + "display_name": "Test", + "id": "fec9de86-51a2-41b0-aef4-ba06eb39e16d", + "name": "Test" + } + ], + "groups": [], + "license_id": "CC-BY-SA-4.0", + "relationships_as_subject": [], + "organization": { + "description": "", + "created": "2016-05-30T11:30:41.710079", + "title": "devVRE", + "name": "devvre", + "is_organization": true, + "state": "active", + "image_url": "", + "revision_id": "a7eee485-a6d5-4a7b-8f73-b0ed999d5b03", + "type": "organization", + "id": "3571cca5-b0ae-4dc6-b791-434a8e062ce5", + "approval_status": "approved" + }, + "name": "my_test_item_devvre", + "isopen": true, + "url": "http://www.d4science.org", + "notes": "A test item of Luca Frosini", + "extras": [ + { + "key": "Item URL", + "value": "https://data.dev.d4science.org/ctlg/devVRE/my_test_item_devvre" + }, + { + "key": "Language", + "value": "EN" + }, + { + "key": "system:cm_item_status", + "value": "approved" + }, + { + "key": "system:cm_item_visibility", + "value": "public" + }, + { + "key": "system:type", + "value": "EmptyProfile" + } + ], + "license_url": "https://creativecommons.org/licenses/by-sa/4.0/", + "ratings_count": 0, + "title": "My Test Item", + "revision_id": "bc0d1f2a-4e97-4810-b951-8b72e8279719" +} +``` + +*Inputs are automatically validated before the request is served.* + +**Usage examples** + +- Example 1 + +``` {.java} +import org.gcube.gcat.client.Item; + +// count item number +Item item = new Item(); +int count = item.count(); +... +``` + +Service Discovery on IS +======================= + +The service can be discovered in the gCore IS as gCore Endpoint with the +following parameter: + +``` {.xml} +org.gcube.data-catalogue +gcat +``` + +The service can be discovered in the Facet Based IS as EService with the +following json query: + +``` {.json} +{ + "@class": "EService", + "consistsOf": [ + { + "@class": "IsIdentifiedBy", + "target": { + "@class": "SoftwareFacet", + "group": "org.gcube.data-catalogue", + "name": "gcat" + } + } + ] +} +``` + +Service Maven Coordinates +========================= + +The maven coordinates of gCat service are: + +``` {.xml} +org.gcube.data-catalogue +gcat +``` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..0ec47bf --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,510 @@ + +*********************************************************** +Welcome to gCube Catalogue Service (aka gCat) documentation +*********************************************************** + +gCat is a RESTful application which exposes operations via REST-API. + +See the available `REST-API docs <../api-docs/index.html>`_. + +Base URL +======== + +In the production environment, its current value is https://api.d4science.org/gcat + + +Authorization +============= + +D4Science adopts state-of-the-art industry standards for authentication and authorization. +Specifically, the implementation fully adopts `OIDC (OpenID Connect) `_ for authentication and UMA 2 (User-Managed Authorization) for authorization flows. +`JSON Web Token (JWT) Access token `_ are used for both authentication and authorization. + +Obtain your Bearer token here: https://dev.d4science.org/how-to-access-resources + +Service +======= + +You can call the methods of the Web Service by writing your own REST client application or using existing REST client plugins. + + +HTTP Statuses +------------- + +Any successful operation returns *200 OK* HTTP status code. +The create operation returns *201 Created*. +Any Background operation returns *202 Accepted*. +Any operation which does not provide any content return *204 No Content*. + + + +The most common error status a client can obtain are: + +* **400 Bad Request** used to indicate a clients error ``_; +* **401 Unauthorized** used to indicate that the client does not provide the authorization token in the HTTP Header or the client has not enough right to perform such request ``_; +* **404 Not Found** used to indicate that the requested instance does not exist ``_; +* **405 Method Not Allowed** the used HTTP method is not supported for the requested URL ``_. + The response contains the *Allow* HTTP Header indicating the supported HTTP method for such URL ``_; +* **409 Conflict** the request could not be completed due to a conflict with the current state of the target resource (e.g. the name of the resource already exists) ``_; +* **500 Internal Server Error** indicate a server failure ``_. + +You can find a complete list of HTTP Status at ``_ + +If you get a *500 Internal Server Error*, please report it in the `gCube ticketing system `_. + +Please use this checklist before reporting an error: + +* Replicate the request; +* The failure could be temporal due to network error, server issue and many other temporal issues. For this reason, please retry the request after a certain amount of time before reporting the issue; +* indicate how to replicate the error; +* indicate the time when the error occurred (this simplifies identifying the issue). + +HTTP Methods +------------ + +gCat is a pure RESTful service. It uses standard HTTP Methods to perform a listing of collections and CRUD (Create Read Update Delete) operations on instances. + + +.. table:: + + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Operation | HTTP Method | URL | Success HTTP Status | Safe | Idempotent | + +==============+=============+========================================+=====================+========+============+ + | Supported | OPTIONS | /{COLLECTION} | 204 No Content | Y | Y | + | HTTP Methods | | | [#allow]_ | | | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | List | GET | /{COLLECTION} | 200 OK | Y | Y | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Count | GET | /{COLLECTION}?count=true | 200 OK | Y | Y | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Exists | HEAD | /{COLLECTION} | 204 No Content | Y | Y | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Create | POST | /{COLLECTION} | 201 Created | N | N | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Supported | OPTIONS | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | Y | Y | + | HTTP Methods | | | [#allow]_ | | | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Exist | HEAD | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | Y | Y | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Read | GET | /{COLLECTION}/{INSTANCE_ID} | 200 OK | Y | Y | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Update | PUT | /{COLLECTION}/{INSTANCE_ID} | 200 OK | N | Y | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Patch | PATCH | /{COLLECTION}/{INSTANCE_ID} | 200 OK | N | Y | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Delete | DELETE | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | N | N [#del]_ | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + | Purge | DELETE | /{COLLECTION}/{INSTANCE_ID}?purge=true | 204 No Content | N | N [#del]_ | + + +-------------+----------------------------------------+---------------------+--------+------------+ + | | PURGE | /{COLLECTION}/{INSTANCE_ID} | 204 No Content | N | N [#del]_ | + +--------------+-------------+----------------------------------------+---------------------+--------+------------+ + +.. [#allow] Supported HTTP Methods in **Allow** HTTP Header + +.. [#del] DELETE has been defined as idempotent. + + *Allamaraju* [#Allamaraju]_ argues that DELETE idempotency should be accomplished client-side. + The server should inform the client if the delete operation succeeded because the resource was really deleted or it was not found, i.e., **404 Not Found** error is suggested instead of **204 No Content**. + The latter situation should be treated as idempotent by the client. + + We share the same vision. For this reason, gCat does not provide server-side idempotency for DELETE and PURGE operations. + +.. [#Allamaraju] Allamaraju S. RESTful Web Services Cookbook: Solutions for Improving Scalability and Simplicity . O’Reilly. first ed. 2010 + + +About URL +^^^^^^^^^ + +The presented URL uses the following convention: + +* **{COLLECTION}** is the plural name of the entity type; +* **{INSTANCE_ID}** is an identification that enables univocally identifying the instance in the collection. + + +About Safety and Idempotency properties +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +* A method is *Safe* if it does not produce any side effects. + "This does not prevent an implementation from including behaviour that is potentially harmful, that is not entirely read-only, or that causes side effects while invoking a safe method" + ``_; +* A method is *Idempotent* if the same operation repeated multiple times has the same side effect than using it one time. + "repeating the request will have the same intended effect, even if the original request succeeded, though the response might differ" + ``_. + +You can find more information about HTTP Methods at ``_ + +Uncommon HTTP Methods +^^^^^^^^^^^^^^^^^^^^^ + +* PATCH method allows to perform a differential update (i.e. an update which provides only the differences and not the whole new representation); +* PURGE method is not a standard but is widely used in service which requires this action + (e.g. `Varnish `_, `Squid `_). + gCat provides support for this method, but to support a wider range of clients, it also provides the Purge action via *DELETE* with the additional get parameter ``purge=true``. + + +Content-Type +------------ + +Any request must contain an indication of the interesting content type. + +The client must specify the **Accept** HTTP Header for any operation returning a result. + +.. code-block:: rest + + Accept: application/json + +For any operation sending content to the service, it is necessary to specify the **Content-Type** HTTP Header. + +.. code-block:: rest + + Content-Type: application/json + +The service accepts and returns only JSON objects. + +`Profile Collection <../api-docs/resource\_Profile.html>`_ instead can be manipulated in XML only. + +Collections +----------- + +The following collections are available to any user. +Catalogue-Editor or above can invoke Non-safe methods only. + +* `Item Collection <../api-docs/resource_Item.html>`_; + + * `Resource Collection <../api-docs/resource_Resource.html>`_; + +* `Profile Collection <../api-docs/resource_Profile.html>`_; +* `Namespace Collection <../api-docs/resource_Namespace.html>`_; +* `License Collection <../api-docs/resource_License.html>`_; +* `Trash Collection <../api-docs/resource_Trash.html>`_; + +The following collections are available for Catalogue-Admins or above only: + +* `Group Collection <../api-docs/resource_Group.html>`_; +* `Organization Collection <../api-docs/resource_Organization.html>`_; +* `User Collection <../api-docs/resource_User.html>`_; +* `Configuration Collection <../api-docs/resource_Configuration.html>`_. + +An overview of the available collections is available at `<../api-docs/index.html>`_; + + +Roles +----- + +Any user has one or more roles in the catalogue. +Only the VRE Manager can assign roles to VRE users. + + +The catalogue uses the following hierarchic roles: + +**Catalogue-Member**: + A user with such a role is mainly capable of listing and reading items; + +**Catalogue-Editor**: + A user with such a role is capable of managing the items he/she creates and capable of using other safe APIs; + +**Catalogue-Admin**: + A user with such a role is capable of administrating many aspects of the catalogue; + +**Catalogue-Manager**: + A user with such a role can use all the APIs exposed by the service except item moderation APIs (e.g. approve, reject, ...). + + +Another role that is not in the role hierarchy: + +**Catalogue-Moderator**: + A user with such a role is capable of invoking the item moderation APIs. + + + .. TIP:: + Please note that not all catalogues are moderated. + +Moderated Catalogues +==================== + + +Any catalogues can be declared as moderated. +This means that, a **Catalogue-Moderator** must approve any submitted items to make them available to the other users of the catalogue. + +In a moderated catalogue, an item can be in the following states: + +**pending**: + The item published by any allowed author (a Catalogue-Editor or above) but not available to the other users of the catalogue. + A Catalogue-Moderator has to approve or reject it; + +**approved**: + A Catalogue-Moderator has approved the item published by any allowed users; + +**rejected**: + A Catalogue-Moderator has rejected the item published by any allowed users. + + + +The following are the moderation operations that an allowed user can perform on an item. +To present the moderation operations, we use the following convention: + + ``initial_state`` ---**operation** (*User/Role performing the operation*)---> ``final_state`` + + +``initial_state`` can be ``none``, meaning the item does not exist. + + +The following are the allowed moderation operation on an item + + ``none`` ---**create** (*Author*)---> ``pending`` + + ``pending`` ---**reject** (*Catalogue-Moderator*)---> ``rejected`` + + ``pending`` ---**approve** (*Catalogue-Moderator*)---> ``approved`` + + ``rejected`` ---**update** (*Author*)---> ``pending`` + + ``approved`` ---**update** (*Author*)---> ``pending`` + + +Please check the table below whcih summarise the item collection operation and the allowed users/roles. + +In a moderated catalogue, both the Catalogue-Moderators and the item author can send messages to +discuss the approval process of the item. The messages are related to a specific item. +Any Catalogue-Moderators receive a message sent by an Author. +The author receives a message sent by a Catalogue-Moderator as well as the other Catalogue-Moderators (if any). + +Messages can be sent both with an action which changes the status of the item or as explicit action which does not change the status of the item: + + ``pending`` ---**message** (*Author OR Catalogue-Moderator*)---> ``pending`` + + ``rejected`` ---**message** (*Author OR Catalogue-Moderator*)---> ``rejected`` + + ``approved`` ---**message** (*Author OR Catalogue-Moderator*)---> ``approved`` + + + +The following table summarize the allowed/forbidden operations depending on: the role of the user and the state of the item. + +.. table:: + + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Operation | Item State | Roles | + + + +----------------------+--------------------------+------------------------------------------+-------------------+ + | | | Catalogue Moderator | Catalogue Admin/Manager | Catalogue Editor | Catalogue Member | + +=====================================+=============+======================+==========================+==========================================+===================+ + | List | Yes all states | Yes all states | Yes only approved - All states if Author | Yes only approved | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Count | Yes all states | Yes all states | Yes only approved - All states if Author | Yes only approved | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Create | 403 Forbidden | Yes -> Pending | Yes -> Pending | 403 Forbidden | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Read | Yes all states | Yes all states | Yes only approved - All states if Author | Yes only approved | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Update | Pending | Yes -> Pending | Yes if Author -> Pending | Yes if Author -> Pending | 403 Forbidden | + + +-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | | Rejected | Yes -> Pending | Yes if Author -> Pending | Yes if Author -> Pending | 403 Forbidden | + + +-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | | Approved | 403 Forbidden | Yes -> Approved | Yes if Author -> Pending | 403 Forbidden | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Delete/Purge | Pending | Yes | Yes | Yes if Author | 403 Forbidden | + + +-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | | Rejected | Yes | Yes | Yes if Author | 403 Forbidden | + + +-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | | Approved | 403 Forbidden | Yes | Yes if Author | 403 Forbidden | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Approve a pending item | Yes | 403 Forbidden | 403 Forbidden | 403 Forbidden | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Reject a pending item | Yes | 403 Forbidden | 403 Forbidden | 403 Forbidden | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + | Message about an item | Yes | Yes if Author | Yes if Author | 403 Forbidden | + +-------------------------------------+-------------+----------------------+--------------------------+------------------------------------------+-------------------+ + + +The Moderation process has associated notification to authors and Catalogue-Moderators. +Please note that the user who has acted is not self-notified, e.g. +approve operation made by a Catalogue-Moderator notifies the item author and the other Catalogue-Moderators of the VRE. + +The following table summarises the addressee of the notification for any action. + + +.. table:: + + +--------------+----------------------+--------+----------------------+ + | Operation | Notified user/role | + + +----------------------+--------+----------------------+ + | | Catalogue-Moderators | Author | User made the action | + +==============+======================+========+======================+ + | Create | Yes | Yes | Yes (custom message) | + +--------------+----------------------+--------+----------------------+ + | Update | Yes | Yes | Yes (custom message) | + +--------------+----------------------+--------+----------------------+ + | Approve | Yes + Social Post if | No | + | | requested (social_post=true) | | + | | and enabled for the VRE | | + +--------------+----------------------+--------+----------------------+ + | Reject | Yes | Yes | No | + +--------------+----------------------+--------+----------------------+ + | Delete/Purge | Yes | Yes | No | + +--------------+----------------------+--------+----------------------+ + | Message | Yes | Yes | No | + +--------------+----------------------+--------+----------------------+ + + + +Java Client +=========== + +We provide the following Java Client out-of-the-box. + + .. TIP:: + If you're coding in Java, it is recommended that you use this Java Client. + +**Maven Coordinates** + +.. code:: xml + + org.gcube.data-catalogue + gcat-client + [2.2.0, 3.0.0-SNAPSHOT) + +**Methods Result** + +The service exposes `its methods <../api-docs/index.html>`_ using a standard naming approach. Moreover, they accept (in the case of HTTP POST/PUT methods) JSON objects. + + .. IMPORTANT:: + The result of all methods is always a JSON object as per below: + +.. code:: javascript + + { + "rating": 0.0, + "license_title": "Creative Commons Attribution Share-Alike 4.0", + "maintainer": "Frosini Luca", + "relationships_as_object": [], + "private": false, + "maintainer_email": "luca.frosini@isti.cnr.it", + "num_tags": 1, + "id": "17051d86-c127-4928-9296-d3d7590161fe", + "metadata_created": "2022-10-17T12:45:53.118318", + "metadata_modified": "2022-10-18T10:30:03.362756", + "author": "Frosini Luca", + "author_email": "luca.frosini@isti.cnr.it", + "state": "active", + "version": null, + "creator_user_id": "f1b0265c-9983-4f97-a7b6-be3cc0544b27", + "type": "dataset", + "resources": [], + "num_resources": 0, + "tags": [ + { + "vocabulary_id": null, + "state": "active", + "display_name": "Test", + "id": "fec9de86-51a2-41b0-aef4-ba06eb39e16d", + "name": "Test" + } + ], + "groups": [], + "license_id": "CC-BY-SA-4.0", + "relationships_as_subject": [], + "organization": { + "description": "", + "created": "2016-05-30T11:30:41.710079", + "title": "devVRE", + "name": "devvre", + "is_organization": true, + "state": "active", + "image_url": "", + "revision_id": "a7eee485-a6d5-4a7b-8f73-b0ed999d5b03", + "type": "organization", + "id": "3571cca5-b0ae-4dc6-b791-434a8e062ce5", + "approval_status": "approved" + }, + "name": "my_test_item_devvre", + "isopen": true, + "url": "http://www.d4science.org", + "notes": "A test item of Luca Frosini", + "extras": [ + { + "key": "Item URL", + "value": "https://data.dev.d4science.org/ctlg/devVRE/my_test_item_devvre" + }, + { + "key": "Language", + "value": "EN" + }, + { + "key": "system:cm_item_status", + "value": "approved" + }, + { + "key": "system:cm_item_visibility", + "value": "public" + }, + { + "key": "system:type", + "value": "EmptyProfile" + } + ], + "license_url": "https://creativecommons.org/licenses/by-sa/4.0/", + "ratings_count": 0, + "title": "My Test Item", + "revision_id": "bc0d1f2a-4e97-4810-b951-8b72e8279719" + } + +*Inputs are automatically validated before the request is served.* + + +**Usage examples** + +- Example 1 + +.. code:: java + + import org.gcube.gcat.client.Item; + + // count item number + Item item = new Item(); + int count = item.count(); + ... + + + +Service Discovery on IS +======================= + +The service can be discovered in the gCore IS as gCore Endpoint with the following parameter: + +.. code:: xml + + org.gcube.data-catalogue + gcat + + +The service can be discovered in the Facet Based IS as EService with the following json query: + +.. code:: json + + { + "@class": "EService", + "consistsOf": [ + { + "@class": "IsIdentifiedBy", + "target": { + "@class": "SoftwareFacet", + "group": "org.gcube.data-catalogue", + "name": "gcat" + } + } + ] + } + + + +Service Maven Coordinates +========================= + +The maven coordinates of gCat service are: + +.. code:: xml + + org.gcube.data-catalogue + gcat + \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..153be5e --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/tests.rst b/docs/tests.rst new file mode 100644 index 0000000..fca9245 --- /dev/null +++ b/docs/tests.rst @@ -0,0 +1,23 @@ +Dev and Pre Users used for moderation tests +======== + +To perform moderation tests in dev and preproduction infrastructure we use different users with the indicated roles. + +.. table:: + + +---------------+---------------+----------------------------------------+ + | User | Username | Role | + +===============+===============+========================================+ + | Mister Blonde | mister.blonde | Catalogue-Admin + Catalogue-Moderator | + +---------------+---------------+----------------------------------------+ + | Mister Blue | mister.blue | Catalogue-Admin | + +---------------+---------------+----------------------------------------+ + | Mister Brown | mister.brown | Catalogue-Moderator | + +---------------+---------------+----------------------------------------+ + | Mister Orange | mister.orange | Catalogue-Editor | + +---------------+---------------+----------------------------------------+ + | Mister Pink | mister.pink | NO ROLE (means Catalogue-Member) | + +---------------+---------------+----------------------------------------+ + | Mister White | mister.white | Catalogue-Manager | + +---------------+---------------+----------------------------------------+ + diff --git a/enunciate.xml b/enunciate.xml new file mode 100644 index 0000000..eb0f240 --- /dev/null +++ b/enunciate.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gcube/extra-resources/WEB-INF/gcube-app.xml b/gcube/extra-resources/WEB-INF/gcube-app.xml new file mode 100644 index 0000000..c056b4f --- /dev/null +++ b/gcube/extra-resources/WEB-INF/gcube-app.xml @@ -0,0 +1,10 @@ + + + + ${project.artifactId} + ${project.groupId} + ${project.version} + ${project.description} + /api-docs.* + /docs.* + \ No newline at end of file diff --git a/gcube/extra-resources/WEB-INF/web.xml b/gcube/extra-resources/WEB-INF/web.xml new file mode 100644 index 0000000..9435c69 --- /dev/null +++ b/gcube/extra-resources/WEB-INF/web.xml @@ -0,0 +1,16 @@ + + + + + org.gcube.data.access.storagehub.StorageHub + + + + org.gcube.data.access.storagehub.StorageHub + /workspace/* + + + \ No newline at end of file diff --git a/gcube/extra-resources/application.yaml b/gcube/extra-resources/application.yaml new file mode 100644 index 0000000..5be945c --- /dev/null +++ b/gcube/extra-resources/application.yaml @@ -0,0 +1,6 @@ +name: IdentityManagerService +group: IAM +version: ${version} +description: ${description} +excludes: + - path: /workspace/api-docs/* \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3b21f70 --- /dev/null +++ b/pom.xml @@ -0,0 +1,209 @@ + + 4.0.0 + + org.gcube.tools + maven-parent + 1.2.0 + + org.gcube.idm + identity-manager + 1.0.0-SNAPSHOT + war + Identity Manager Service + Identity Manager Smargears Service + + + UTF-8 + 11 + 11 + + ${project.basedir}${file.separator}src${file.separator}main${file.separator}webapp${file.separator}WEB-INF + 2.14.0 + + + + + scm:git:https://code-repo.d4science.org/gCubeSystem/${project.artifactId}.git + + scm:git:https://code-repo.d4science.org/gCubeSystem/${project.artifactId}.git + https://code-repo.d4science.org/gCubeSystem/${project.artifactId} + + + + + + org.gcube.distribution + gcube-smartgears-bom + 3.0.1-SNAPSHOT + pom + import + + + + + + + org.slf4j + slf4j-api + + + org.gcube.common + common-security + + + org.gcube.common + keycloak-client + + + org.keycloak + keycloak-admin-client + 21.0.1 + + + org.glassfish.jersey.media + jersey-media-multipart + + + org.glassfish.jersey.containers + jersey-container-servlet + + + javax.ws.rs + javax.ws.rs-api + + + + org.gcube.core + common-smartgears-app + + + + org.gcube.core + common-smartgears + + + + + + com.webcohesion.enunciate + enunciate-core-annotations + ${enunciate.version} + provided + + + com.webcohesion.enunciate + enunciate-rt-util + ${enunciate.version} + provided + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + + junit + junit + 4.11 + test + + + ch.qos.logback + logback-classic + test + + + org.gcube.portlets.user + uri-resolver-manager + [1.0.0, 2.0.0-SNAPSHOT) + + + + + + ${project.artifactId} + + + + kr.motd.maven + sphinx-maven-plugin + 2.10.0 + + + ${project.build.directory}/${project.artifactId}-${project.version}/docs + html + ${basedir}/docs + ${basedir}/docs + + + + process-resources + + generate + + + + + + + + + com.webcohesion.enunciate + enunciate-maven-plugin + ${enunciate.version} + + + assemble + + assemble + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-enunciate-docs + process-resources + + copy-resources + + + target + + + + ${project.build.directory}/${project.artifactId}-${project.version}/api-docs + + ${project.build.directory}/api-docs + true + + + + + + + + + + org.apache.maven.plugins + maven-war-plugin + + true + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/idm/IdentityManagerExceptionMapper.java b/src/main/java/org/gcube/idm/IdentityManagerExceptionMapper.java new file mode 100644 index 0000000..fd68b3e --- /dev/null +++ b/src/main/java/org/gcube/idm/IdentityManagerExceptionMapper.java @@ -0,0 +1,38 @@ +package org.gcube.idm; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +/** + * @author Alfredo Oliviero (ISTI - CNR) + */ +@Provider +public class IdentityManagerExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(Exception exception) { + + Status status = Status.INTERNAL_SERVER_ERROR; + String exceptionMessage = exception.getMessage(); + try { + if(exception.getCause() != null) { + exceptionMessage = exception.getCause().getMessage(); + } + } catch(Exception e) { + exceptionMessage = exception.getMessage(); + } + MediaType mediaType = MediaType.TEXT_PLAIN_TYPE; + + if(WebApplicationException.class.isAssignableFrom(exception.getClass())) { + Response gotResponse = ((WebApplicationException) exception).getResponse(); + status = Status.fromStatusCode(gotResponse.getStatusInfo().getStatusCode()); + } + + return Response.status(status).entity(exceptionMessage).type(mediaType).build(); + } + +} diff --git a/src/main/java/org/gcube/idm/IdentityManagerResourceInitializer.java b/src/main/java/org/gcube/idm/IdentityManagerResourceInitializer.java new file mode 100644 index 0000000..6b873a8 --- /dev/null +++ b/src/main/java/org/gcube/idm/IdentityManagerResourceInitializer.java @@ -0,0 +1,25 @@ +package org.gcube.idm; + +import javax.ws.rs.ApplicationPath; + +import org.gcube.idm.rest.GreetingsRest; +import org.gcube.smartgears.annotations.ManagedBy; +import org.glassfish.jersey.server.ResourceConfig; + +/** + * @author Alfredo Oliviero (ISTI - CNR) + */ + + + // SMARTGEARS +// legge i parametri del service da application.yaml + +@ApplicationPath("/") +@ManagedBy(IdentityManagerdInitializator.class) +public class IdentityManagerResourceInitializer extends ResourceConfig { + + public IdentityManagerResourceInitializer() { + packages(GreetingsRest.class.getPackage().toString()); + } + +} diff --git a/src/main/java/org/gcube/idm/IdentityManagerdInitializator.java b/src/main/java/org/gcube/idm/IdentityManagerdInitializator.java new file mode 100644 index 0000000..a7fb592 --- /dev/null +++ b/src/main/java/org/gcube/idm/IdentityManagerdInitializator.java @@ -0,0 +1,68 @@ +package org.gcube.idm; + +import org.gcube.common.security.providers.SecretManagerProvider; +import org.gcube.smartgears.ApplicationManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Alfredo Oliviero (ISTI - CNR) + */ +public class IdentityManagerdInitializator implements ApplicationManager { + + /** + * Logger + */ + private static Logger logger = LoggerFactory.getLogger(IdentityManagerdInitializator.class); + + public static boolean initialised; + + /** + * {@inheritDoc} + */ + @Override + public synchronized void onInit() { + + String context = SecretManagerProvider.instance.get().getContext(); + + logger.trace( + "\n-------------------------------------------------------\n" + + "Identity Manager Service is Starting on context {}\n" + + "-------------------------------------------------------", + context); + +// ApplicationContext applicationContext = ContextProvider.get(); +// String helloWorldEServiceID = applicationContext.id(); + + logger.trace( + "\n-------------------------------------------------------\n" + + "Identity Manager Service Started Successfully on context {}\n" + + "-------------------------------------------------------", + context); + + + + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void onShutdown(){ + + String context = SecretManagerProvider.instance.get().getContext(); + + logger.trace( + "\n-------------------------------------------------------\n" + + "Identity Manager Service is Stopping on context {}\n" + + "-------------------------------------------------------", + context); + + + logger.trace( + "\n-------------------------------------------------------\n" + + "Identity Manager Service Stopped Successfully on context {}\n" + + "-------------------------------------------------------", + context); + } +} diff --git a/src/main/java/org/gcube/idm/rest/GreetingsRest.java b/src/main/java/org/gcube/idm/rest/GreetingsRest.java new file mode 100644 index 0000000..3867527 --- /dev/null +++ b/src/main/java/org/gcube/idm/rest/GreetingsRest.java @@ -0,0 +1,69 @@ +package org.gcube.idm.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; + +import com.webcohesion.enunciate.metadata.rs.RequestHeader; +import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceGroup; +import com.webcohesion.enunciate.metadata.rs.ResourceLabel; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; + +@Path("greetings") +@ResourceGroup("Greetings APIs") +@ResourceLabel("Greetings APIs") +@RequestHeaders ({ + @RequestHeader( name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources") +}) +public class GreetingsRest { + + @GET + @Produces({"application/json;charset=UTF-8", "application/vnd.api+json"}) + public String list(@QueryParam("limit") @DefaultValue("10") int limit, + @QueryParam("offset") @DefaultValue("0") int offset) { + + return "[\"saluti\",\"saluti_volgari\"]"; + } + + @POST + @Produces("application/json;charset=UTF-8") + @Consumes("application/json;charset=UTF-8") + public String create(String json) { + //Greeting g = new Greeting(); + //return g.create(json); + return "{\"text\":\"hi\"}"; + + } + + @PUT + @Path("/{greeting_name}") + @Consumes("application/json;charset=UTF-8") + @Produces("application/json;charset=UTF-8") + @StatusCodes ({ + @ResponseCode ( code = 200, condition = "The greeting has been updated successfully.") + }) +// @AuthorizationControl(allowedRoles={"boss"}, exception=NotAuthorizedException.class) + public String update(@PathParam("greeting_name") String name, String json) { + return "{}"; + } + + + @DELETE + @Path("/{greeting_name}") + @StatusCodes ({ + @ResponseCode ( code = 204, condition = "The item has been deleted successfully."), + @ResponseCode ( code = 404, condition = "The item was not found.") + }) + public String delete(@PathParam("greeting_name") String name) { + return "{}"; + } +} diff --git a/src/main/java/org/gcube/idm/rest/UsersRest.java b/src/main/java/org/gcube/idm/rest/UsersRest.java new file mode 100644 index 0000000..c454b6a --- /dev/null +++ b/src/main/java/org/gcube/idm/rest/UsersRest.java @@ -0,0 +1,153 @@ +package org.gcube.idm.rest; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.policies.Users; +import org.gcube.common.security.Owner; +import org.gcube.common.security.providers.SecretManagerProvider; +import org.gcube.keycloack.KeycloackApiClient; +import org.gcube.keycloack.KeycloakAPIFactory; +import org.gcube.rest.ResponseBean; +import org.gcube.smartgears.ContextProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.gcube.smartgears.security.SimpleCredentials; +import org.gcube.smartgears.security.defaults.DefaultAuthorizationProvider; +import org.gcube.smartgears.utils.InnerMethodName; +import org.jboss.resteasy.spi.NotImplementedYetException; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.slf4j.LoggerFactory; + +import com.webcohesion.enunciate.metadata.rs.RequestHeader; +import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceGroup; +import com.webcohesion.enunciate.metadata.rs.ResourceLabel; + +@Path("2/users") +@ResourceGroup("Users APIs") +@ResourceLabel("Greetings APIs") +@RequestHeaders({ + @RequestHeader(name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources") +}) +public class UsersRest { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Users.class); + + @GET + @Path("/{get-usernames-by-role}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public Response getUsernamesByRole( + @QueryParam("role-name") String roleName) { + Status status = Status.OK; + ResponseBean responseBean = new ResponseBean(); + + List usernames = new ArrayList(); + try { + String ctx = SecretManagerProvider.instance.get().getContext(); + KeycloackApiClient keycloackApiClient = KeycloakAPIFactory.getSingleton().createtKeycloakInstance(ctx); + + List users = searchByRole(keycloackApiClient, roleName); + if (users != null) { + for (UserRepresentation user : users) { + usernames.add(user.getUsername()); + } + } + responseBean.setResult(usernames); + responseBean.setSuccess(true); + } catch (Exception e) { + logger.error("Unable to retrieve user with the requested role", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + + } + + private static List searchByRole(KeycloackApiClient keycloackApiClient, String roleName) { + logger.info("Searching by role: {}", roleName); + + List clients = keycloackApiClient.kclient.realm(keycloackApiClient.realmName) + .clients().findByClientId(keycloackApiClient.clientIdContext); + + String id = ""; + for (ClientRepresentation client : clients) { + logger.info("found client =" + client.getClientId()); + logger.info("found client id=" + client.getId()); + id = client.getId(); + } + + List users = keycloackApiClient.kclient.realm(keycloackApiClient.realmName) + .clients() + .get(id).roles().get(roleName) + .getUserMembers(0, 100000); + return users; + } + + @GET + @Path("/{get-profile}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public String getCurrentProfile() { + // SMARTGEARS Specializza il tracciamento della chiamata su Accounting + InnerMethodName.instance.set("getCurrentProfile"); + Owner owner = SecretManagerProvider.instance.get().getOwner(); + + ApplicationContext appContext = ContextProvider.get(); + SimpleCredentials credentials = ((DefaultAuthorizationProvider) appContext.container().authorizationProvider()) + .getCredentials(); + + String ctx = SecretManagerProvider.instance.get().getContext(); + KeycloackApiClient keycloackApiClient = KeycloakAPIFactory.getSingleton().createtKeycloakInstance(ctx); + return null; + } + + @GET + @Path("/{get-email}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public String getCurrentEmail() { + throw new NotImplementedYetException(); + } + + @GET + @Path("/{get-fullname}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public String getCurrentFullname() { + throw new NotImplementedYetException(); + } + + @GET + @Path("/{get-all-usernames}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public String getAllUsernames() { + throw new NotImplementedYetException(); + } + + @GET + @Path("/{get-all-fullnames-and-usernames}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public String getAllUsernamesFullnames() { + throw new NotImplementedYetException(); + } + + @GET + @Path("/{user-exists}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public boolean checkUserExists() { + throw new NotImplementedYetException(); + } + + @GET + @Path("/{get-oauth-profile}") + @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) + public boolean getCurrentOAuthProfile() { + throw new NotImplementedYetException(); + } + +} diff --git a/src/main/java/org/gcube/keycloack/ErrorMessages.java b/src/main/java/org/gcube/keycloack/ErrorMessages.java new file mode 100644 index 0000000..538dcdc --- /dev/null +++ b/src/main/java/org/gcube/keycloack/ErrorMessages.java @@ -0,0 +1,34 @@ +package org.gcube.keycloack; + +public class ErrorMessages { + + protected static final String NOT_USER_TOKEN_CONTEXT_USED = "User's information can only be retrieved through a user token (not qualified)"; + protected static final String CANNOT_RETRIEVE_SERVICE_ENDPOINT_INFORMATION = "Unable to retrieve such service endpoint information"; + + private static final String NO_RUNTIME_RESOURCE_TEMPLATE_NAME_CATEGORY = "There is no Runtime Resource having name %s and Category %s in this scope"; + + protected static final String no_runtime_category(String runtime, String category) { + return String.format(NO_RUNTIME_RESOURCE_TEMPLATE_NAME_CATEGORY, runtime, category); + } + + // public static final String MISSING_TOKEN = "Missing token."; + // public static final String MISSING_PARAMETERS = "Missing request + // parameters."; + // public static final String INVALID_TOKEN = "Invalid token."; + // public static final String TOKEN_GENERATION_APP_FAILED = "Token generation + // failed."; + // public static final String NOT_APP_TOKEN = "Invalid token: not belonging to + // an application."; + // public static final String NOT_APP_ID = "Invalid application id: it doesn't + // belong to an application."; + // public static final String NO_APP_PROFILE_FOUND = "There is no application + // profile for this app id/scope."; + // public static final String BAD_REQUEST = "Please check the parameter you + // passed, it seems a bad request"; + // public static final String ERROR_IN_API_RESULT = "The error is reported into + // the 'message' field of the returned object"; + // public static final String POST_OUTSIDE_VRE = "A post cannot be written into + // a context that is not a VRE"; + // public static final String DEPRECATED_METHOD = "This method is deprecated, + // must use version 2"; +} diff --git a/src/main/java/org/gcube/keycloack/KeycloackApiClient.java b/src/main/java/org/gcube/keycloack/KeycloackApiClient.java new file mode 100644 index 0000000..20df1f5 --- /dev/null +++ b/src/main/java/org/gcube/keycloack/KeycloackApiClient.java @@ -0,0 +1,23 @@ +package org.gcube.keycloack; + +import org.keycloak.admin.client.Keycloak; + +public class KeycloackApiClient { + public Keycloak kclient; + public String realmName; + public String clientIdContext; + public String context; + + public static String getClientIdContext(String context){ + return context.replace("/", "%2F"); + } + + public KeycloackApiClient(Keycloak kclient, String realmName, String context) { + this.clientIdContext = getClientIdContext(context); + this.context = context; + this.kclient = kclient; + this.realmName = realmName; + // ClientsResource clients = kclient.realm(realmName).clients(); + + } +} diff --git a/src/main/java/org/gcube/keycloack/KeycloackClientParams_UNUSED.java b/src/main/java/org/gcube/keycloack/KeycloackClientParams_UNUSED.java new file mode 100644 index 0000000..738de48 --- /dev/null +++ b/src/main/java/org/gcube/keycloack/KeycloackClientParams_UNUSED.java @@ -0,0 +1,102 @@ +package org.gcube.keycloack; + +import java.io.InputStream; +import java.net.URL; +import java.util.AbstractMap.SimpleEntry; +import java.util.Map.Entry; +import java.util.Properties; + +import javax.ws.rs.InternalServerErrorException; + +import org.gcube.common.security.providers.SecretManagerProvider; +import org.gcube.common.keycloak.DefaultKeycloakClient; +import org.gcube.common.keycloak.KeycloakClientException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KeycloackClientParams_UNUSED { + + private static final Logger logger = LoggerFactory.getLogger(KeycloackClientParams_UNUSED.class); + + public static final String CATALOGUE_NAME = "IDM"; + + protected static final String CLIENT_ID_SECRET_FILENAME = "config.properties"; + protected static final String CLIENT_ID_PROPERTY_NAME = "clientId"; + + public String context; + public String clientId; + public String clientSecret; + public DefaultKeycloakClient gcubeKeycloakClient; + + // Reads the property file and extracts the keycloack configuration params + protected static Entry getClientIdAndClientSecret(String context) { + try { + Properties properties = new Properties(); + ClassLoader classLoader = KeycloackClientParams_UNUSED.class.getClassLoader(); + URL url = classLoader.getResource(CLIENT_ID_SECRET_FILENAME); + logger.trace("Going to read {} at {}", CLIENT_ID_SECRET_FILENAME, url.toString()); + InputStream input = classLoader.getResourceAsStream(CLIENT_ID_SECRET_FILENAME); + properties.load(input); + + String clientId = "IDM"; + if (properties.containsKey(CLIENT_ID_PROPERTY_NAME)) { + clientId = properties.getProperty(CLIENT_ID_PROPERTY_NAME); + } + + int index = context.indexOf('/', 1); + String root = context.substring(0, index == -1 ? context.length() : index); + String clientSecret = properties.getProperty(root); + + SimpleEntry entry = new SimpleEntry(clientId, clientSecret); + return entry; + } catch (Exception e) { + throw new InternalServerErrorException( + "Unable to retrieve Application Token for context " + + SecretManagerProvider.instance.get().getContext(), + e); + } + } + + // TODO: VERIFICARE + public URL getRealmBaseURL() throws KeycloakClientException { + return this.gcubeKeycloakClient.getRealmBaseURL(this.context); + } + + public URL getRealmBaseURL(String realm) throws KeycloakClientException { + return this.gcubeKeycloakClient.getRealmBaseURL(this.context, realm); + } + + public URL getServerURL() { + try { + return this.getRealmBaseURL(); + } catch (KeycloakClientException e) { + // That should be almost impossible + logger.warn("Cannot create base URL", e); + return null; + } + } + + public String getClientid() { + return clientId; + } + + // TODO: serve? implementare + public String getPassword() { + return null; + } + + // TODO: VERIFICARE + public String getRealm() { + return this.context; + } + + public KeycloackClientParams_UNUSED(String context) { + this.context = context; + Entry params = getClientIdAndClientSecret(context); + this.clientId = params.getKey(); + this.clientSecret = params.getKey(); + + this.gcubeKeycloakClient = new DefaultKeycloakClient(); + } + +} diff --git a/src/main/java/org/gcube/keycloack/KeycloakAPIFactory.java b/src/main/java/org/gcube/keycloack/KeycloakAPIFactory.java new file mode 100644 index 0000000..8ea513e --- /dev/null +++ b/src/main/java/org/gcube/keycloack/KeycloakAPIFactory.java @@ -0,0 +1,147 @@ +package org.gcube.keycloack; + +import static org.gcube.resources.discovery.icclient.ICFactory.clientFor; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.Iterator; +import java.util.List; + +import org.gcube.common.encryption.encrypter.StringEncrypter; +import org.gcube.common.resources.gcore.ServiceEndpoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.gcube.smartgears.ContextProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KeycloakAPIFactory { + private static final Logger logger = LoggerFactory.getLogger(KeycloakAPIFactory.class); + + private final static String RUNTIME_RESOURCE_NAME = "IAM"; + private final static String CATEGORY = "Service"; + + // the singleton obj + + private static KeycloakAPIFactory singleton = new KeycloakAPIFactory(); + + // properties that it contains + private String keycloakURL; + private String realm; + private String clientid; + private String password; + + /** + * Private constructor + */ + private KeycloakAPIFactory() { + logger.info("Building KeycloakAPICredentials object"); + + lookupPropertiesFromIs(); + logger.info("KeycloakAPICredentials object built"); + } + + /** + * Read the properties from the infrastructure + */ + private void lookupPropertiesFromIs() { + + logger.info("Starting creating KeycloakAPICredentials"); + + // String ctx = SecretManagerProvider.instance.get().getContext(); + // TODO: verificare che sia contesto corretto + ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears + + logger.info("Discovering liferay user's credentials in context " + + ctx.container().configuration().infrastructure()); + + try { + List resources = getConfigurationFromIS(); + if (resources.size() == 0) { + logger.error("There is no Runtime Resource having name " + RUNTIME_RESOURCE_NAME + " and Category " + + CATEGORY + " in this scope."); + throw new Exception("There is no Runtime Resource having name " + RUNTIME_RESOURCE_NAME + + " and Category " + CATEGORY + " in this scope."); + } else { + for (ServiceEndpoint res : resources) { + Iterator accessPointIterator = res.profile().accessPoints().iterator(); + while (accessPointIterator.hasNext()) { + ServiceEndpoint.AccessPoint accessPoint = (ServiceEndpoint.AccessPoint) accessPointIterator + .next(); + + if (accessPoint.name().equals("d4science")) { + keycloakURL = accessPoint.address(); + realm = accessPoint.name(); + clientid = accessPoint.username(); + password = StringEncrypter.getEncrypter().decrypt(accessPoint.password()); + logger.info("Found accesspoint URL = " + keycloakURL); + } + } + } + + } + } catch (Exception e) { + logger.error("Unable to retrieve such service endpoint information!", e); + return; + // }finally{ + // if(oldContext != null) + // ScopeProvider.instance.set(oldContext); + } + + logger.info("Bean built " + toString()); + } + + /** + * Retrieve endpoints information from IS for DB + * + * @return list of endpoints for ckan database + * @throws Exception + */ + private List getConfigurationFromIS() throws Exception { + SimpleQuery query = queryFor(ServiceEndpoint.class); + query.addCondition("$resource/Profile/Name/text() eq '" + RUNTIME_RESOURCE_NAME + "'"); + query.addCondition("$resource/Profile/Category/text() eq '" + CATEGORY + "'"); + DiscoveryClient client = clientFor(ServiceEndpoint.class); + List toReturn = client.submit(query); + return toReturn; + } + + public static KeycloakAPIFactory getSingleton() { + if (singleton == null) + singleton = new KeycloakAPIFactory(); + return singleton; + } + + public String getServerURL() { + return keycloakURL; + } + + public String getClientid() { + return clientid; + } + + public String getPassword() { + return password; + } + + public String getRealm() { + return realm; + } + + public KeycloackApiClient createtKeycloakInstance(String context) { + // String clientIdContext = KeycloackUtils.getClientIdContext(context); + String realm = this.getRealm(); + Keycloak keycloak = KeycloakBuilder.builder() + .serverUrl(this.getServerURL()) + .realm(realm) + .grantType(OAuth2Constants.CLIENT_CREDENTIALS) + .clientId(this.getClientid()) // + .clientSecret(this.getPassword()).build(); + return new KeycloackApiClient(keycloak, realm, context); + } + +} diff --git a/src/main/java/org/gcube/rest/ResponseBean.java b/src/main/java/org/gcube/rest/ResponseBean.java new file mode 100644 index 0000000..84daeac --- /dev/null +++ b/src/main/java/org/gcube/rest/ResponseBean.java @@ -0,0 +1,71 @@ +package org.gcube.rest; +import java.io.Serializable; + +/** + * Response bean + * + */ +public class ResponseBean implements Serializable { + + private static final long serialVersionUID = -2725238162673879658L; + /** + * The result of the request: true if it succeeded, false otherwise + */ + private boolean success; + + /** + * An error message if something wrong happened, null/empty otherwise + */ + private String message; + /** + * The result object of the request + */ + private Object result; + + public ResponseBean() { + super(); + } + + /** + * @param success + * @param message + * @param result + */ + public ResponseBean(boolean success, String message, Object result) { + super(); + this.success = success; + this.message = message; + this.result = result; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + @Override + public String toString() { + return "ResponseBean [success=" + success + + ", message=" + message + ", result=" + result + "]"; + } +} + diff --git a/src/main/java/org/gcube/rest/annotation/PATCH.java b/src/main/java/org/gcube/rest/annotation/PATCH.java new file mode 100644 index 0000000..bd46340 --- /dev/null +++ b/src/main/java/org/gcube/rest/annotation/PATCH.java @@ -0,0 +1,17 @@ +package org.gcube.rest.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.ws.rs.HttpMethod; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@HttpMethod("PATCH") +public @interface PATCH { +} \ No newline at end of file diff --git a/src/main/java/org/gcube/rest/annotation/PURGE.java b/src/main/java/org/gcube/rest/annotation/PURGE.java new file mode 100644 index 0000000..004980d --- /dev/null +++ b/src/main/java/org/gcube/rest/annotation/PURGE.java @@ -0,0 +1,17 @@ +package org.gcube.rest.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.ws.rs.HttpMethod; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@HttpMethod("PURGE") +public @interface PURGE { +} \ No newline at end of file diff --git a/src/main/java/org/gcube/rest/utils/HTTPUtility.java b/src/main/java/org/gcube/rest/utils/HTTPUtility.java new file mode 100644 index 0000000..6b5f9e9 --- /dev/null +++ b/src/main/java/org/gcube/rest/utils/HTTPUtility.java @@ -0,0 +1,66 @@ +package org.gcube.rest.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.gxhttp.request.GXHTTPStringRequest; +import org.gcube.keycloack.KeycloackClientParams_UNUSED; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class HTTPUtility { + + private static final Logger logger = LoggerFactory.getLogger(HTTPUtility.class); + + public static StringBuilder getStringBuilder(InputStream inputStream) throws IOException { + StringBuilder result = new StringBuilder(); + try(BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while((line = reader.readLine()) != null) { + result.append(line); + } + } + + return result; + } + + public static GXHTTPStringRequest createGXHTTPStringRequest(String url, String path, boolean post) + throws UnsupportedEncodingException { + GXHTTPStringRequest gxhttpStringRequest = GXHTTPStringRequest.newRequest(url); + gxhttpStringRequest.from(KeycloackClientParams_UNUSED.CATALOGUE_NAME); + if(post) { + gxhttpStringRequest.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + } + gxhttpStringRequest.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); + gxhttpStringRequest.path(path); + return gxhttpStringRequest; + } + + public static String getResultAsString(HttpURLConnection httpURLConnection) throws IOException { + int responseCode = httpURLConnection.getResponseCode(); + if(responseCode >= Status.BAD_REQUEST.getStatusCode()) { + Status status = Status.fromStatusCode(responseCode); + InputStream inputStream = httpURLConnection.getErrorStream(); + StringBuilder result = getStringBuilder(inputStream); + logger.trace(result.toString()); + throw new WebApplicationException(result.toString(), status); + } + InputStream inputStream = httpURLConnection.getInputStream(); + String ret = getStringBuilder(inputStream).toString(); + logger.trace("Got Respose is {}", ret); + return ret; + } + +} diff --git a/src/main/java/org/gcube/utils/IdmConstantsaa/IdmConstants.java b/src/main/java/org/gcube/utils/IdmConstantsaa/IdmConstants.java new file mode 100644 index 0000000..3b1b958 --- /dev/null +++ b/src/main/java/org/gcube/utils/IdmConstantsaa/IdmConstants.java @@ -0,0 +1,12 @@ +package org.gcube.utils.IdmConstantsaa; + +public class IdmConstants { + + public static final String SERVICE_CLASS = "org.gcube.data-catalogue"; + public static final String SERVICE_NAME = "gcat"; + + public final static String CONFIGURATION_CATEGORY = IdmConstants.SERVICE_CLASS; + public final static String CONFIGURATION_NAME = IdmConstants.SERVICE_NAME + "-configuration"; + + public static final String SERVICE_ENTRY_NAME = "org.gcube.gcat.ResourceInitializer"; +} diff --git a/src/main/resources/META-INF/enunciate/d4science_docs.fmt b/src/main/resources/META-INF/enunciate/d4science_docs.fmt new file mode 100644 index 0000000..a5ee6ab --- /dev/null +++ b/src/main/resources/META-INF/enunciate/d4science_docs.fmt @@ -0,0 +1,1183 @@ +[#ftl] +[#-- + + Copyright © 2006-2016 Web Cohesion (info@webcohesion.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--] +[#-- @ftlvariable name="resourceApis" type="java.util.List" --] +[#-- @ftlvariable name="serviceApis" type="java.util.List" --] +[#-- @ftlvariable name="data" type="java.util.List" --] +[#-- @ftlvariable name="downloads" type="java.util.List" --] +[#-- @ftlvariable name="title" type="java.lang.String" --] +[#-- @ftlvariable name="indexPageName" type="java.lang.String" --] +[#-- @ftlvariable name="disableMountpoint" type="java.lang.Boolean" --] +[#-- @ftlvariable name="disableResourceLinks" type="java.lang.Boolean" --] +[#-- @ftlvariable name="apiRelativePath" type="java.lang.String" --] +[#-- @ftlvariable name="cssFile" type="java.lang.String" --] +[#-- @ftlvariable name="additionalCssFiles" type="java.util.List" --] +[#-- @ftlvariable name="copyright" type="java.lang.String" --] +[#-- @ftlvariable name="apiDoc" type="java.lang.String" --] +[#-- @ftlvariable name="swaggerUI" type="com.webcohesion.enunciate.api.InterfaceDescriptionFile" --] +[#-- @ftlvariable name="favicon" type="java.lang.String" --] +[#-- @ftlvariable name="includeApplicationPath" type="java.lang.Boolean" --] +[#-- @ftlvariable name="includeDataTypesHomeIndex" type="java.lang.Boolean" --] +[#--set up the subnavigation menus--] +[#assign nav_sections = { } /] +[#if resourceApis?size > 0] + [#assign nav_sections = nav_sections + { "Resources" : "resources.html" }/] +[/#if] +[#if serviceApis?size > 0] + [#assign nav_sections = nav_sections + { "Services" : "services.html" }/] +[/#if] +[#if data?size > 0] + [#list data as syntax] + [#assign nav_sections = { syntax.label : syntax.slug + ".html" } /] + [/#list] +[/#if] +[#if downloads?size > 0] + [#assign nav_sections = nav_sections + { "Files and Libraries" : "downloads.html"} /] +[/#if] +[#--Basic boilerplate macro.--] +[#macro boilerplate title=title breadcrumbs=[{"title" : "Home", "href" : indexPageName}] pagenav=[] codeblocks=true] + + + + + + + + + ${title} + + + + + + + + [#if cssFile??] + + + [/#if] + [#list additionalCssFiles as additionalCssFile] + + [/#list] + [#if favicon??] + + + [/#if] + + + + + + +
+ D4Science + D4Science + + Don't have a D4Science account? + + Create one + + Could not find what you are looking for? + + Contact us. +
+ + + +
+
+ + +
+ + + [#nested/] + +
+
+

[#if copyright??]Copyright © ${copyright}. [/#if]Generated by Enunciate.

+
+
+ +
+
+
+ + + + + + + + + + + + + + +[/#macro] +[#--Macro that wraps its text in a deprecated tag if the element is deprecated.--] +[#macro deprecation element] + [#assign deprecated=(element?? && element.deprecated??)/] + [#if deprecated][/#if][#nested/][#if deprecated][/#if] +[/#macro] +[@file name=indexPageName] + [#assign pagenav=[]/] + [#if resourceApis?size > 0] + [#assign pagenav=pagenav + [{ "href" : "#resources", "title" : "Resources" }]/] + [/#if] + [#if serviceApis?size > 0] + [#assign pagenav=pagenav + [{ "href" : "#services", "title" : "Services" }]/] + [/#if] + [#list data as syntax] + [#assign pagenav=pagenav + [{ "href" : "#" + syntax.slug, "title" : syntax.label }]/] + [/#list] + [@boilerplate pagenav=pagenav] + [#if apiDoc??] + + [/#if] + [#if resourceApis?size > 0] + +

Resources

+ [#list resourceApis as resourceApi] + [#if downloads?size > 0] + +

+ The resources use a data model that is supported by a set of client-side libraries that are made available on the + files and libraries page. +

+ [/#if] + [#if resourceApi.wadlFile??] + +

+ There is a WADL document available that describes the resources API. +

+ [/#if] + [#if swaggerUI??] + +

+ You may also enjoy the interactive interface provided for this API by Swagger. +

+

+ Try it out! +

+ [/#if] + + + + + [#if resourceApi.includeResourceGroupName!false] + + [/#if] + + + + + + + [#list resourceApi.resourceGroups as resourceGroup] + [@processResourceGroup resourceGroup=resourceGroup/] + + [#if resourceApi.includeResourceGroupName!false] + + [/#if] + + + + + [/#list] + +
namepathmethodsdescription
[@deprecation element=resourceGroup]${resourceGroup.label}[/@deprecation]
    [#list resourceGroup.paths as path]
  • [@deprecation element=resourceGroup][#if ((includeApplicationPath!false) && (resourceGroup.relativeContextPath?has_content))]/${resourceGroup.relativeContextPath}[/#if]${path.path}[/@deprecation]
  • [/#list]
    [#list resourceGroup.paths as path]
  • [@deprecation element=resourceGroup][#list path.methods as method]${method} [/#list][/@deprecation]
  • [/#list]
[@deprecation element=resourceGroup]${resourceGroup.description!" "}[/@deprecation]
+ [/#list] + [/#if] + [#if serviceApis?size > 0] + +

Services

+ [#list serviceApis as serviceApi] + [#list serviceApi.serviceGroups as serviceGroup] + + + + + + + + + + + [#list serviceGroup.services as service] + [@processService service=service/] + + + + + [/#list] + +
Namespace ${serviceGroup.namespace!"(Default)"}[#if serviceGroup.wsdlFile??] (wsdl)[/#if]
namedescription
[@deprecation element=service]${service.label}[/@deprecation][@deprecation element=service]${service.description!" "}[/@deprecation]
+ [/#list] + [/#list] + [#if downloads?size > 0] + +

The services API is also accessible by a set of client-side libraries that can be downloaded from the files and libraries page.

+ [/#if] + [/#if] + [#if data?size > 0 && includeDataTypesHomeIndex] + +

Data Types

+ [#list data as syntax] + [@processDataSyntax syntax=syntax/] + +

${syntax.label}

+ [#list syntax.namespaces as ns] + [#if ns.types?size > 0] + + + [#if ns.uri??] + [#if ns.uri?length > 0] + + [#else] + + [/#if] + [/#if] + + + + + + + + [#list ns.types as type] + + + + + [/#list] + +
Namespace ${ns.uri}[#if ns.schemaFile??] (schema)[/#if]Default Namespace [#if ns.schemaFile??] (schema)[/#if]
typedescription
[@deprecation element=type]${type.label}[/@deprecation][@deprecation element=type]${type.description}[/@deprecation]
+ [/#if] + [/#list] + [/#list] + [#elseif data?size > 0] + [#list data as syntax] + [@processDataSyntax syntax=syntax/] + [/#list] + [/#if] + [/@boilerplate] +[/@file] +[@file name="data.html"] + [#assign pagenav=[]/] + [#list data as syntax] + [#assign pagenav=pagenav + [{ "href" : "#" + syntax.slug, "title" : syntax.label }]/] + [/#list] + [@boilerplate title=title + ": Data Types" breadcrumbs=[{"title" : "Home", "href" : indexPageName}, {"title" : "Data Types" , "href" : "data.html"}] pagenav=pagenav] +

Data Types

+ [#list data as syntax] + +

${syntax.label}

+ [#list syntax.namespaces as ns] + [#if ns.types?size > 0] + + + [#if ns.uri??] + [#if ns.uri?length > 0] + + [#else] + + [/#if] + [/#if] + + + + + + + + [#list ns.types as type] + + + + + [/#list] + +
Namespace ${ns.uri}[#if ns.schemaFile??] (schema)[/#if]Default Namespace [#if ns.schemaFile??] (schema)[/#if]
typedescription
[@deprecation element=type]${type.label}[/@deprecation][@deprecation element=type]${type.description}[/@deprecation]
+ [/#if] + [/#list] + [/#list] + [/@boilerplate] +[/@file] +[#if downloads?size > 0] + [@file name="downloads.html"] + [#assign pagenav=[]/] + [#list downloads as download] + [#assign pagenav=pagenav + [{ "href" : "#" + download.slug, "title" : download.name }]/] + [/#list] + [@boilerplate title=title + ": Files and Libraries" breadcrumbs=[{"title" : "Home", "href" : indexPageName}, { "title" : "Files and Libraries" , "href" : "downloads.html"}] codeblocks=true pagenav=pagenav] +

Files and Libraries

+ + [#list downloads as download] +

${download.name}

+ [#if download.created??] +

Created ${download.created?date?string.long}

+ [/#if] + [#if download.artifactId??] +
+ [#if download.groupId??] +
groupId
+
${download.groupId}
+ [/#if] + [#if download.artifactId??] +
artifactId
+
${download.artifactId}
+ [/#if] + [#if download.version??] +
version
+
${download.version}
+
+ [/#if] + [/#if] + [#if download.description??] +

${download.description}

+ [/#if] + + + + + + + + + + + [#list download.files as file] + + + + + + [/#list] + +
Files
namesizedescription
${file.name}${file.size}${file.description!download.description!" "}
+ [/#list] + [/@boilerplate] + [/@file] +[/#if] +[#if resourceApis?size > 0] + [@file name="resources.html"] + [@boilerplate title=title + ": Resources" breadcrumbs=[{"title" : "Home", "href" : indexPageName}, {"title" : "Resources" , "href" : "resources.html"}]] +

Resources

+ + [#list resourceApis as resourceApi] + [#if downloads?size > 0] +

+ The resources use a data model that is supported by a set of client-side libraries that are made available on the + files and libraries page. +

+ [/#if] + [#if resourceApi.wadlFile??] +

+ There is a WADL document available that describes the resources API. +

+ [/#if] + [#if swaggerUI??] +

+ You may also enjoy the interactive interface provided for this API by Swagger. +

+

+ Try it out! +

+ [/#if] + + + + [#if resourceApi.includeResourceGroupName!false] + + [/#if] + + + + + + + [#list resourceApi.resourceGroups as resourceGroup] + + [#if resourceApi.includeResourceGroupName!false] + + [/#if] + + + + + [/#list] + +
namepathmethodsdescription
[@deprecation element=resourceGroup]${resourceGroup.label}[/@deprecation]
    [#list resourceGroup.paths as path]
  • [@deprecation element=resourceGroup][#if ((includeApplicationPath!false) && (resourceGroup.relativeContextPath?has_content))]/${resourceGroup.relativeContextPath}[/#if]${path.path}[/@deprecation]
  • [/#list]
    [#list resourceGroup.paths as path]
  • [@deprecation element=resourceGroup][#list path.methods as method]${method} [/#list][/@deprecation]
  • [/#list]
[@deprecation element=resourceGroup]${resourceGroup.description!" "}[/@deprecation]
+ [/#list] + [/@boilerplate] + [/@file] +[/#if] +[#if serviceApis?size > 0] + [@file name="services.html"] + [@boilerplate title=title + ": Services" breadcrumbs=[{"title" : "Home", "href" : indexPageName}, {"title" : "Services" , "href" : "services.html"}]] +

Services

+ [#list serviceApis as serviceApi] + [#list serviceApi.serviceGroups as serviceGroup] + + + + + + + + + + + [#list serviceGroup.services as service] + + + + + [/#list] + +
Namespace ${serviceGroup.namespace}[#if serviceGroup.wsdlFile??] (wsdl)[/#if]
namedescription
[@deprecation element=service]${service.label}[/@deprecation][@deprecation element=service]${service.description!" "}[/@deprecation]
+ [/#list] + [/#list] + [#if downloads?size > 0] + +

The services API is also accessible by a set of client-side libraries that can be downloaded from the files and libraries page.

+ [/#if] + [/@boilerplate] + [/@file] +[/#if] +[#macro processResourceGroup resourceGroup] + [#assign pagenav=[]/] + [#list resourceGroup.resources as resource] + [#list resource.methods as method] + [#assign path=resource.path/] + [#if ((includeApplicationPath!false) && (resourceGroup.relativeContextPath?has_content))] + [#assign path="/" + resourceGroup.relativeContextPath + resource.path/] + [/#if] + [#assign pagenav=pagenav + [{ "href" : "#" + method.slug, "title" : method.label + " " + path }]/] + [/#list] + [/#list] + [#-- @ftlvariable name="resourceGroup" type="com.webcohesion.enunciate.api.resources.ResourceGroup" --] + [@file name=resourceGroup.slug + ".html"] + [@boilerplate title=title + ": " + resourceGroup.label breadcrumbs=[{"title" : "Home", "href" : indexPageName}, {"title" : "Resources" , "href" : "resources.html"}, {"title" : resourceGroup.label , "href" : resourceGroup.slug + ".html"}] pagenav=pagenav] +

${resourceGroup.label} Resource

+ [#if resourceGroup.description??] + +

${resourceGroup.description}

+ [/#if] + [#list resourceGroup.resources as resource] + [#if resource.since?? || resource.version?? || resource.seeAlso??] + +
+ [#if resource.since??] +
Available Since
+
${resource.since}
+ [/#if] + [#if resource.version??] +
Version
+
${resource.version}
+ [/#if] + [#if resource.seeAlso??] + [#list resource.seeAlso as seeAlso] +
See Also
+
${seeAlso}
+ [/#list] + [/#if] +
+ [/#if] + [#list resource.methods as method] + +
+

${method.label} [#if ((includeApplicationPath!false) && (resourceGroup.relativeContextPath?has_content))]/${resourceGroup.relativeContextPath}[/#if]${resource.path}[#if !disableResourceLinks!false] [/#if]

+ [#if resourceGroup.deprecated?? || method.deprecated??] + +
This method has been deprecated. [#if method.deprecated??] ${method.deprecated!""}[#else] ${resource.deprecated!""}[/#if]
+ [/#if] + [#if method.description??] + +

${method.description}

+ [/#if] + [#-- would be nice to enable a "Try it out" link to Swagger. See https://github.com/swagger-api/swagger-spec/issues/239 + [#if swaggerUI??] + +

Try it out!

+ [/#if] + --] + [#assign securityRoles=method.securityRoles![]/] + [#if (method.since?? || method.version?? || method.seeAlso?? || securityRoles?size > 0)] + +
+ [#if method.since??] +
Available Since
+
${method.since}
+ [/#if] + [#if method.version??] +
Version
+
${method.version}
+ [/#if] + [#if securityRoles?size > 0] +
Security Roles Allowed
+
[#list securityRoles as role]${role}[#if role_has_next], [/#if][/#list]
+ [/#if] + [#if method.seeAlso??] + [#list method.seeAlso as seeAlso] +
See Also
+
${seeAlso}
+ [/#list] + [/#if] +
+ [/#if] + [#if method.parameters?size > 0] + + + + + + + + + [#assign includeDefault=method.includeDefaultParameterValues/] + [#if includeDefault] + + [/#if] + [#assign includeConstraints=method.hasParameterConstraints/] + [#if includeConstraints] + + [/#if] + [#assign includeMultiplicity=method.hasParameterMultiplicity/] + [#if includeMultiplicity] + + [/#if] + + + + [#list method.parameters as parameter] + + + + + [#if includeDefault] + + [/#if] + [#if includeConstraints] + + [/#if] + [#if includeMultiplicity] + + [/#if] + + [/#list] + +
Request Parameters
nametypedescriptiondefaultconstraintsmultivalued
${parameter.name}${parameter.typeLabel}${parameter.description!" "}${parameter.defaultValue!" "}${parameter.constraints!" "}${parameter.multivalued?string("yes", "no")}
+ [/#if] + [#if method.requestEntity??] + + + + + + + + [#if method.requestEntity.description??] + + [/#if] + + + + [#list method.requestEntity.mediaTypes as io] + + + + [#if io_index = 0 && method.requestEntity.description??] + 1] rowspan="${method.requestEntity.mediaTypes?size}" class="multi-row-description"[/#if]>${method.requestEntity.description} + [/#if] + + [/#list] + +
Request Body
media typedata typedescription
${io.mediaType}[@referenceDataType referenceType=io.dataType!{"label" : "(custom)"}/][#if io.syntax??] (${io.syntax})[/#if]
+ [/#if] + [#if method.responseCodes?size > 0] + + + + + + + + [#assign hasExpectedTypes=false/] + [#list method.responseCodes as responseCode] + [#if responseCode.mediaTypes?size > 0] + [#assign hasExpectedTypes=true/] + [/#if] + [/#list] + [#if hasExpectedTypes] + + [/#if] + + + + [#list method.responseCodes as responseCode] + + + + [#if hasExpectedTypes] + + [/#if] + + [/#list] + +
Response Codes
codeconditiontype
${responseCode.code}${responseCode.condition!""}
    [#list responseCode.mediaTypes as io]
  • [@referenceDataType referenceType=io.dataType!{"label" : "(custom)"}/][#if io.syntax??] (${io.syntax})[/#if]
  • [/#list]
+ [/#if] + [#if method.responseEntity??] + + + + + + + + [#if method.responseEntity.description??] + + [/#if] + + + + [#list method.responseEntity.mediaTypes as io] + + + + [#if io_index = 0 && method.responseEntity.description??] + 1] rowspan="${method.responseEntity.mediaTypes?size}" class="multi-row-description"[/#if]>${method.responseEntity.description} + [/#if] + + [/#list] + +
Response Body
media typedata typedescription
${io.mediaType}[@referenceDataType referenceType=io.dataType!{"label" : "(custom)"}/][#if io.syntax??] (${io.syntax})[/#if]
+ [/#if] + [#if method.warnings?size > 0] + + + + + + + + + + + [#list method.warnings as responseCode] + + + + + [/#list] + +
Response Warnings
codecondition
${responseCode.code}${responseCode.condition!""}
+ [/#if] + [#if method.responseHeaders?size > 0] + + + + + + + + + + + [#list method.responseHeaders as header] + + + + + [/#list] + +
Response Headers
namedescription
${header.name}${header.description!" "}
+ + [/#if] + [#if method.example??] +

Example

+ +
+
+
+
Request
+
+${method.example.requestHeaders?xhtml}
+            [#if method.example.requestLang??]
+                
+${method.example.requestBody?xhtml}
+                
+            [/#if]
+              
+
+
+
Response
+
+${method.example.responseHeaders?xhtml}
+            [#if method.example.responseLang??]
+                
+${method.example.responseBody?xhtml}
+                
+            [/#if]
+              
+
+
+
+ [/#if] +
+ [/#list] + [/#list] + [/@boilerplate] + [/@file] +[/#macro] +[#macro processService service] + [#assign pagenav=[]/] + [#list service.operations as operation] + [#assign pagenav=pagenav + [{ "href" : "#" + operation.slug, "title" : operation.name }]/] + [/#list] + [#-- @ftlvariable name="service" type="com.webcohesion.enunciate.api.services.Service" --] + [@file name=service.slug + ".html"] + [@boilerplate title=title + ": " + service.label breadcrumbs=[{"title" : "Home", "href" : indexPageName}, {"title" : service.label , "href" : service.slug + ".html"}] pagenav=pagenav] +

${service.label} Service

+ [#if service.deprecated??] + +
This service has been deprecated. ${service.deprecated}
+ [/#if] + [#if service.description??] + +

${service.description}

+ [/#if] + +
+ [#if service.namespace?? && service.namespace?length > 0] +
Namespace
+
${service.namespace}
+ [/#if] + [#if service.group.wsdlFile??] +
WSDL
+
${service.group.wsdlFile.href}
+ [/#if] + [#if service.path??] +
Path
+
${service.path}
+ [/#if] + [#if service.since??] +
Available Since
+
${service.since}
+ [/#if] + [#if service.version??] +
Version
+
${service.version}
+ [/#if] + [#if service.seeAlso??] + [#list service.seeAlso as seeAlso] +
See Also
+
${seeAlso}
+ [/#list] + [/#if] +
+ [#list service.operations as operation] + +

${operation.name} Operation

+ [#if operation.deprecated??] + +
This method has been deprecated. ${operation.deprecated}
+ [/#if] + [#if operation.description??] + +

${operation.description}

+ [/#if] + [#if (operation.since?? || operation.version?? || operation.seeAlso??)] + +
+ [#if operation.since??] +
Available Since
+
${operation.since}
+ [/#if] + [#if operation.version??] +
Version
+
${operation.version}
+ [/#if] + [#if operation.seeAlso??] + [#list operation.seeAlso as seeAlso] +
See Also
+
${seeAlso}
+ [/#list] + [/#if] +
+ [/#if] + [#if operation.inputParameters?size > 0] + + + + + + + + + + + + [#list operation.inputParameters as parameter] + + + + + + [/#list] + +
Input Parameters
nametypedescription
${parameter.name}[@referenceDataType referenceType=parameter.dataType/]${parameter.description!" "}
+ [/#if] + [#if operation.httpRequestHeaders?size > 0] + + + + + + + + + + + + [#list operation.httpRequestHeaders as parameter] + + + + + + [/#list] + +
HTTP Request Headers
nametypedescription
${parameter.name}[@referenceDataType referenceType=parameter.dataType/]${parameter.description!" "}
+ [/#if] + [#if operation.outputParameters?size > 0] + + + + + + + + + + + + [#list operation.outputParameters as parameter] + + + + + + [/#list] + +
Output Parameters
nametypedescription
${parameter.name}[@referenceDataType referenceType=parameter.dataType/]${parameter.description!" "}
+ [/#if] + [#if operation.returnType??] + + + + + + + + + + + + + + + +
Return Value
typedescription
[@referenceDataType referenceType=operation.returnType/]${operation.returnDescription!" "}
+ [/#if] + [#if operation.faults?size > 0] + + + + + + + + + + + + [#list operation.faults as fault] + + + + + + [/#list] + +
Faults
nametypeconditions
${fault.name}[@referenceDataType referenceType=fault.dataType/]${fault.conditions!" "}
+ [/#if] + [/#list] + [/@boilerplate] + [/@file] +[/#macro] +[#macro processDataSyntax syntax] + [#-- @ftlvariable name="syntax" type="com.webcohesion.enunciate.api.datatype.Syntax" --] + [@file name=syntax.slug + ".html"] + [@boilerplate title=title + ": " + syntax.label breadcrumbs=[{"title" : "Home", "href" : indexPageName}, {"title" : syntax.label , "href" : syntax.slug + ".html"} ]] +

${syntax.label}

+ [#list syntax.namespaces as ns] + [#if ns.types?size > 0] + + + [#if ns.uri??] + [#if ns.uri?length > 0] + + [#else] + + [/#if] + [/#if] + + + + + + + + [#list ns.types as type] + [@processDataType type=type/] + + + + + [/#list] + +
Namespace ${ns.uri}[#if ns.schemaFile??] (schema)[/#if]Default Namespace [#if ns.schemaFile??] (schema)[/#if]
typedescription
[@deprecation element=type]${type.label}[/@deprecation][@deprecation element=type]${type.description}[/@deprecation]
+ [/#if] + [/#list] + [/@boilerplate] + [/@file] +[/#macro] +[#macro processDataType type] + [#-- @ftlvariable name="type" type="com.webcohesion.enunciate.api.datatype.DataType" --] + [#assign pagenav=[]/] + [#if type.values??] + [#list type.values as value] + [#assign pagenav=pagenav + [{ "href" : "#" + value.value, "title" : value.value }]/] + [/#list] + [/#if] + [@file name=type.slug + ".html"] + [@boilerplate title=title + ": " + type.label breadcrumbs=[{"title" : "Home", "href" : indexPageName}, {"title" : type.syntax.label , "href" : type.syntax.slug + ".html"}, {"title" : type.label , "href" : type.slug + ".html"} ] pagenav=pagenav codeblocks=true] +

${type.label} Data Type

+ [#if type.deprecated??] + +
This data type has been deprecated. ${type.deprecated}
+ [/#if] + [#if type.description??] + +

${type.description}

+ [/#if] + +
+ [#if type.namespace.uri??] +
Namespace
+ [#if type.namespace.uri?length > 0] +
${type.namespace.uri}
+ [#else] +
(Default)
+ [/#if] + [/#if] + [#if type.namespace.schemaFile??] +
Schema
+
${type.namespace.schemaFile.href}
+ [/#if] + [#if type.since??] +
Available Since
+
${type.since}
+ [/#if] + [#if type.version??] +
Version
+
${type.version}
+ [/#if] + [#if type.abstract] +
Abstract Type
+
+ [/#if] + [#if type.subtypes??] +
Subtypes
+
[#list type.subtypes as subtype][#if subtype.slug??]${subtype.label}[#else]${subtype.label}[/#if][#if subtype_has_next], [/#if][/#list]
+ [/#if] + [#if type.interfaces??] +
Implemented Interfaces
+
[#list type.interfaces as iface]${iface.label}[#if iface_has_next], [/#if][/#list]
+ [/#if] + [#if type.seeAlso??] + [#list type.seeAlso as seeAlso] +
See Also
+
${seeAlso}
+ [/#list] + [/#if] +
+ [#if type.values??] + + + + + + + + + + + [#list type.values as value] + + + + + [/#list] + +
Values
valuedescription
${value.value}[#if value.since??]since ${value.since} [/#if]${value.description!" "}
+ [/#if] + [#if type.properties??] + + + + + + + + [#list type.propertyMetadata?keys as meta] + + [/#list] + + + + + [#list type.properties as property] + + + + [#list type.propertyMetadata?keys as meta] + + [/#list] + + + [/#list] + + [#if type.supertypes??] + [#list type.supertypes as supertype] + [#if supertype.value?? && supertype.value.properties?? && supertype.value.properties?size > 0] + + + + + [#list supertype.value.properties as superProperty] + + + + [#list type.propertyMetadata?keys as meta] + + [/#list] + + + [/#list] + + [/#if] + [/#list] + [/#if] +
Properties
namedata type${type.propertyMetadata[meta]}description
[@deprecation element=property]${property.name}[/@deprecation][@deprecation element=property][@referenceDataType referenceType=property.dataType/][/@deprecation][@deprecation element=property][@printPropertyMetadata property=property meta=meta/][/@deprecation][@deprecation element=property][#if property.since??]since ${property.since} [/#if]${property.description!" "}[/@deprecation]
Properties inherited from ${supertype.label}
${superProperty.name}[@referenceDataType referenceType=superProperty.dataType/][@printPropertyMetadata property=superProperty meta=meta/][#if superProperty.since??]since ${superProperty.since} [/#if]${superProperty.description!" "}
+ [/#if] + [#if type.example??] + +

Example

+ [#if type.abstract && type.subtypes?? ] + +
This data type is abstract. The example below may be incomplete. More accurate examples can be found in subtypes pages.
+ [/#if] + +
${type.example.body?xhtml}
+ [/#if] + [/@boilerplate] + [/@file] +[/#macro] +[#macro referenceDataType referenceType] +[#-- @ftlvariable name="type" type="com.webcohesion.enunciate.api.datatype.DataTypeReference" --] +[#if referenceType.containers??][#list referenceType.containers as container]${container?string} of [/#list][/#if][#if referenceType.slug??][/#if]${referenceType.label!"(custom)"}[#if referenceType.slug??][/#if] +[/#macro] +[#macro printPropertyMetadata property meta] + [#assign metaValue=property[meta]!({ "structure" : true })/] + [#if metaValue?is_hash && metaValue.structure!false] +[#if metaValue.href??][/#if][#if metaValue.title??][/#if]${metaValue.value!" "}[#if metaValue.title??][/#if][#if metaValue.href??][/#if] + [#else] +${metaValue} + [/#if] +[/#macro] \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/.gitignore b/src/main/webapp/WEB-INF/.gitignore new file mode 100644 index 0000000..a252987 --- /dev/null +++ b/src/main/webapp/WEB-INF/.gitignore @@ -0,0 +1,5 @@ +/LICENSE.md +/README.md +/changelog.xml +/gcube-app.xml +/CHANGELOG.md diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..ef9a39b --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + + + org.gcube.acme.ResourceInitializer + + + default + /docs/* + + + default + /api-docs/* + + + org.gcube.acme.ResourceInitializer + /* + + \ No newline at end of file diff --git a/src/main/webapp/docs/css/d4science_enunciate_custom.css b/src/main/webapp/docs/css/d4science_enunciate_custom.css new file mode 100644 index 0000000..9dae183 --- /dev/null +++ b/src/main/webapp/docs/css/d4science_enunciate_custom.css @@ -0,0 +1,25 @@ +.d4science_intro { + top: 0; + z-index: 2000; + position: fixed; + display: block ruby; + padding: 10px; + background: white; + width: 100%; +} + +.navbar-fixed-top { + top: 100px !important; +} + +.sidebar { + top: 160px !important; +} + +.navbar { + margin-bottom: 40px !important; +} + +.main { + top: 90px; +} \ No newline at end of file