diff --git a/.github/workflows/site_deploy.yml b/.github/workflows/site_deploy.yml index f1cbabd00c..f4e9b7615c 100644 --- a/.github/workflows/site_deploy.yml +++ b/.github/workflows/site_deploy.yml @@ -210,7 +210,6 @@ jobs: if: ${{ github.event.inputs.server_only != 'true' }} run: | set -ux - cd vcell-client mvn clean install -DskipTests - name: deploy installers and singularity to kubernetes site and web help to vcell.org if: ${{ github.event.inputs.deployment_type == 'kubernetes' }} diff --git a/pom.xml b/pom.xml index 8fd9f5b665..95d2c38db2 100644 --- a/pom.xml +++ b/pom.xml @@ -122,7 +122,7 @@ 4.4.13 2.3.0 ${imagej2.version} - 1.4.8 + 2.1.7 2.15.2 2.15.2 2.15.2 diff --git a/python-restclient/.openapi-generator/FILES b/python-restclient/.openapi-generator/FILES index 0bdd74cbb5..e52351bd6f 100644 --- a/python-restclient/.openapi-generator/FILES +++ b/python-restclient/.openapi-generator/FILES @@ -2,6 +2,7 @@ .gitignore README.md docs/AccesTokenRepresentationRecord.md +docs/AdminResourceApi.md docs/BioModel.md docs/BioModelResourceApi.md docs/BiomodelRef.md @@ -21,6 +22,7 @@ test/__init__.py tox.ini vcell_client/__init__.py vcell_client/api/__init__.py +vcell_client/api/admin_resource_api.py vcell_client/api/bio_model_resource_api.py vcell_client/api/hello_world_api.py vcell_client/api/publication_resource_api.py diff --git a/python-restclient/README.md b/python-restclient/README.md index 7ff52aa1db..ec430585c0 100644 --- a/python-restclient/README.md +++ b/python-restclient/README.md @@ -62,19 +62,24 @@ configuration = vcell_client.Configuration( host = "https://vcellapi-test.cam.uchc.edu" ) +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. # Enter a context with an instance of the API client with vcell_client.ApiClient(configuration) as api_client: # Create an instance of the API class - api_instance = vcell_client.BioModelResourceApi(api_client) - bio_model_id = 'bio_model_id_example' # str | + api_instance = vcell_client.AdminResourceApi(api_client) try: - # Delete the BioModel from VCell's database. - api_instance.delete_bio_model(bio_model_id) + # Get usage summary + api_response = api_instance.get_usage() + print("The response of AdminResourceApi->get_usage:\n") + pprint(api_response) except ApiException as e: - print("Exception when calling BioModelResourceApi->delete_bio_model: %s\n" % e) + print("Exception when calling AdminResourceApi->get_usage: %s\n" % e) ``` @@ -84,6 +89,7 @@ All URIs are relative to *https://vcellapi-test.cam.uchc.edu* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- +*AdminResourceApi* | [**get_usage**](docs/AdminResourceApi.md#get_usage) | **GET** /api/v1/admin/usage | Get usage summary *BioModelResourceApi* | [**delete_bio_model**](docs/BioModelResourceApi.md#delete_bio_model) | **DELETE** /api/v1/bioModel/{bioModelID} | Delete the BioModel from VCell's database. *BioModelResourceApi* | [**get_biomodel_by_id**](docs/BioModelResourceApi.md#get_biomodel_by_id) | **GET** /api/v1/bioModel/{bioModelID} | Get BioModel information in JSON format by ID. *BioModelResourceApi* | [**upload_bio_model**](docs/BioModelResourceApi.md#upload_bio_model) | **POST** /api/v1/bioModel/upload_bioModel | Upload the BioModel to VCell database. Returns BioModel ID. diff --git a/python-restclient/docs/AdminResourceApi.md b/python-restclient/docs/AdminResourceApi.md new file mode 100644 index 0000000000..14577c365e --- /dev/null +++ b/python-restclient/docs/AdminResourceApi.md @@ -0,0 +1,75 @@ +# vcell_client.AdminResourceApi + +All URIs are relative to *https://vcellapi-test.cam.uchc.edu* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**get_usage**](AdminResourceApi.md#get_usage) | **GET** /api/v1/admin/usage | Get usage summary + + +# **get_usage** +> bytearray get_usage() + +Get usage summary + +### Example + +```python +import time +import os +import vcell_client +from vcell_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to https://vcellapi-test.cam.uchc.edu +# See configuration.py for a list of all supported configuration parameters. +configuration = vcell_client.Configuration( + host = "https://vcellapi-test.cam.uchc.edu" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Enter a context with an instance of the API client +with vcell_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = vcell_client.AdminResourceApi(api_client) + + try: + # Get usage summary + api_response = api_instance.get_usage() + print("The response of AdminResourceApi->get_usage:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling AdminResourceApi->get_usage: %s\n" % e) +``` + + + +### Parameters +This endpoint does not need any parameter. + +### Return type + +**bytearray** + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/pdf + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | The PDF report | - | +**401** | Not Authorized | - | +**403** | Not Allowed | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/python-restclient/docs/UsersResourceApi.md b/python-restclient/docs/UsersResourceApi.md index 8d65eaa020..a3fa40a974 100644 --- a/python-restclient/docs/UsersResourceApi.md +++ b/python-restclient/docs/UsersResourceApi.md @@ -332,7 +332,7 @@ Name | Type | Description | Notes ### HTTP request headers - **Content-Type**: application/json - - **Accept**: application/json + - **Accept**: text/plain ### HTTP response details | Status code | Description | Response headers | diff --git a/python-restclient/test/test_admin_resource_api.py b/python-restclient/test/test_admin_resource_api.py new file mode 100644 index 0000000000..64bb394e1c --- /dev/null +++ b/python-restclient/test/test_admin_resource_api.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest + +from vcell_client.api.admin_resource_api import AdminResourceApi + + +class TestAdminResourceApi(unittest.TestCase): + """AdminResourceApi unit test stubs""" + + def setUp(self) -> None: + self.api = AdminResourceApi() + + def tearDown(self) -> None: + pass + + def test_get_usage(self) -> None: + """Test case for get_usage + + Get usage summary + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/python-restclient/vcell_client/__init__.py b/python-restclient/vcell_client/__init__.py index 948e9b291a..7911d0d4e7 100644 --- a/python-restclient/vcell_client/__init__.py +++ b/python-restclient/vcell_client/__init__.py @@ -18,6 +18,7 @@ __version__ = "1.0.0" # import apis into sdk package +from vcell_client.api.admin_resource_api import AdminResourceApi from vcell_client.api.bio_model_resource_api import BioModelResourceApi from vcell_client.api.hello_world_api import HelloWorldApi from vcell_client.api.publication_resource_api import PublicationResourceApi diff --git a/python-restclient/vcell_client/api/__init__.py b/python-restclient/vcell_client/api/__init__.py index 165b900f3f..874b9febc3 100644 --- a/python-restclient/vcell_client/api/__init__.py +++ b/python-restclient/vcell_client/api/__init__.py @@ -1,6 +1,7 @@ # flake8: noqa # import apis into api package +from vcell_client.api.admin_resource_api import AdminResourceApi from vcell_client.api.bio_model_resource_api import BioModelResourceApi from vcell_client.api.hello_world_api import HelloWorldApi from vcell_client.api.publication_resource_api import PublicationResourceApi diff --git a/python-restclient/vcell_client/api/admin_resource_api.py b/python-restclient/vcell_client/api/admin_resource_api.py new file mode 100644 index 0000000000..57f6f83e54 --- /dev/null +++ b/python-restclient/vcell_client/api/admin_resource_api.py @@ -0,0 +1,295 @@ +# coding: utf-8 + +""" + VCell API + + VCell API + + The version of the OpenAPI document: 1.0.1 + Contact: vcell_support@uchc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import io +import warnings + +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Dict, List, Optional, Tuple, Union, Any + +try: + from typing import Annotated +except ImportError: + from typing_extensions import Annotated + +from typing import Union + + +from vcell_client.api_client import ApiClient +from vcell_client.api_response import ApiResponse +from vcell_client.rest import RESTResponseType + + +class AdminResourceApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + def get_usage( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> bytearray: + """Get usage summary + + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_usage_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "bytearray", + '401': None, + '403': None + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def get_usage_with_http_info( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[bytearray]: + """Get usage summary + + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_usage_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "bytearray", + '401': None, + '403': None + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def get_usage_without_preload_content( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Get usage summary + + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_usage_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "bytearray", + '401': None, + '403': None + + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _get_usage_serialize( + self, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> Tuple: + + _host = None + + _collection_formats: Dict[str, str] = { + + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[str, str] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/pdf' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'openId' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/api/v1/admin/usage', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/python-restclient/vcell_client/api/users_resource_api.py b/python-restclient/vcell_client/api/users_resource_api.py index 86eadbf9c9..1b534654bf 100644 --- a/python-restclient/vcell_client/api/users_resource_api.py +++ b/python-restclient/vcell_client/api/users_resource_api.py @@ -1291,7 +1291,7 @@ def _map_user_serialize( # set the HTTP header `Accept` _header_params['Accept'] = self.api_client.select_header_accept( [ - 'application/json' + 'text/plain' ] ) diff --git a/tools/AdminResourceApi.patch b/tools/AdminResourceApi.patch new file mode 100644 index 0000000000..a2569427f0 --- /dev/null +++ b/tools/AdminResourceApi.patch @@ -0,0 +1,43 @@ +diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/api/AdminResourceApi.java b/vcell-restclient/src/main/java/org/vcell/restclient/api/AdminResourceApi.java +index 376df77e9..be5b616c3 100644 +--- a/vcell-restclient/src/main/java/org/vcell/restclient/api/AdminResourceApi.java ++++ b/vcell-restclient/src/main/java/org/vcell/restclient/api/AdminResourceApi.java +@@ -17,17 +17,12 @@ import org.vcell.restclient.ApiException; + import org.vcell.restclient.ApiResponse; + import org.vcell.restclient.Pair; + +-import java.io.File; ++import java.io.*; + + import com.fasterxml.jackson.core.type.TypeReference; + import com.fasterxml.jackson.databind.ObjectMapper; + +-import java.io.InputStream; +-import java.io.ByteArrayInputStream; +-import java.io.ByteArrayOutputStream; + import java.io.File; +-import java.io.IOException; +-import java.io.OutputStream; + import java.net.http.HttpRequest; + import java.nio.channels.Channels; + import java.nio.channels.Pipe; +@@ -111,10 +106,16 @@ public class AdminResourceApi { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("getUsage", localVarResponse); + } ++ // Create a temporary file to store the PDF ++ File tempFile = File.createTempFile("usage_summary", ".pdf"); ++ try (OutputStream out = new FileOutputStream(tempFile)) { ++ // Write the response body to the file ++ localVarResponse.body().transferTo(out); ++ } + return new ApiResponse( +- localVarResponse.statusCode(), +- localVarResponse.headers().map(), +- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream ++ localVarResponse.statusCode(), ++ localVarResponse.headers().map(), ++ tempFile + ); + } finally { + } diff --git a/tools/generate.sh b/tools/generate.sh index 3a306af66c..45532d9d23 100755 --- a/tools/generate.sh +++ b/tools/generate.sh @@ -22,6 +22,15 @@ docker run --rm -v ${parentDir}:/vcell \ -o /vcell/vcell-restclient \ -c /vcell/tools/java-config.yaml +# Apply the patch to AdminResourceApi.java to treat getUsage() as a PDF file rather than a JSON file +pushd "${parentDir}" || { echo "Failed to change directory to ${parentDir}"; exit 1; } +if ! git apply "${scriptDir}/AdminResourceApi.patch"; then + echo "Failed to apply AdminResourceApi.patch" + exit 1 +fi +popd || { echo "Failed to return to the previous directory"; exit 1; } + + docker run --rm -v ${parentDir}:/vcell \ ${generatorCliImage} generate \ -g python \ diff --git a/tools/openapi.yaml b/tools/openapi.yaml index 2ddb56629a..74758808a4 100644 --- a/tools/openapi.yaml +++ b/tools/openapi.yaml @@ -15,6 +15,27 @@ info: servers: - url: https://vcellapi-test.cam.uchc.edu paths: + /api/v1/admin/usage: + get: + tags: + - Admin Resource + summary: Get usage summary + operationId: getUsage + responses: + "200": + description: The PDF report + content: + application/pdf: + schema: + format: binary + type: string + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - openId: + - admin /api/v1/bioModel/upload_bioModel: post: tags: diff --git a/vcell-core/src/main/java/cbit/vcell/publish/ITextWriter.java b/vcell-core/src/main/java/cbit/vcell/publish/ITextWriter.java index 7a227ae45c..002f3b974f 100644 --- a/vcell-core/src/main/java/cbit/vcell/publish/ITextWriter.java +++ b/vcell-core/src/main/java/cbit/vcell/publish/ITextWriter.java @@ -147,7 +147,6 @@ public abstract class ITextWriter { public static final String PDF_WRITER = "PDF_WRITER"; public static final String HTM_WRITER = "HTML_WRITER"; - public static final String RTF_WRITER = "RTF_WRITER"; protected static int DEF_IMAGE_WIDTH = 400; protected static int DEF_IMAGE_HEIGHT = 400; protected static final int DEF_GEOM_WIDTH = 150; @@ -833,8 +832,6 @@ public static ITextWriter getInstance(String writerType) { newWriter = new PDFWriter(); } else if (ITextWriter.HTM_WRITER.equals(writerType)) { newWriter = new HTMWriter(); - } else if (ITextWriter.RTF_WRITER.equals(writerType)) { - newWriter = new RTFWriter(); } else { throw new IllegalArgumentException("Unsupported writer type: " + writerType); } @@ -942,7 +939,7 @@ public void writeBioModel(BioModel bioModel, FileOutputStream fos, PageFormat pa int chapterNum = 1; if (preferences.includePhysio()) { Chapter physioChapter = new Chapter("Physiology For " + name, chapterNum++); - introSection = physioChapter.addSection("General Info", physioChapter.numberDepth() + 1); + introSection = physioChapter.addSection("General Info", physioChapter.getNumberDepth() + 1); String freeTextAnnotation = bioModel.getVCMetaData().getFreeTextAnnotation(bioModel); writeMetadata(introSection, name, freeTextAnnotation, userName, "BioModel"); writeModel(physioChapter, bioModel.getModel()); @@ -953,7 +950,7 @@ public void writeBioModel(BioModel bioModel, FileOutputStream fos, PageFormat pa if (simContexts.length > 0) { Chapter simContextsChapter = new Chapter("Applications For " + name, chapterNum++); if (introSection == null) { - introSection = simContextsChapter.addSection("General Info", simContextsChapter.numberDepth() + 1); + introSection = simContextsChapter.addSection("General Info", simContextsChapter.getNumberDepth() + 1); String freeTextAnnotation = bioModel.getVCMetaData().getFreeTextAnnotation(bioModel); writeMetadata(introSection, name, freeTextAnnotation, userName, "BioModel"); } @@ -1047,7 +1044,7 @@ protected void writeFilamentRegionEquation(Section container, FilamentRegionEqua protected void writeGeom(Section container, Geometry geom, GeometryContext geomCont) throws Exception { try { - Section geomSection = container.addSection("Geometry: " + geom.getName(), container.numberDepth() + 1); + Section geomSection = container.addSection("Geometry: " + geom.getName(), container.getNumberDepth() + 1); if (geom.getDimension() == 0) { Paragraph p = new Paragraph(new Phrase("Non spatial geometry.")); p.setAlignment(Paragraph.ALIGN_CENTER); @@ -1107,9 +1104,9 @@ public void writeGeometry(Geometry geom, FileOutputStream fos, PageFormat pageFo Section introSection = null; int chapterNum = 1; Chapter geomChapter = new Chapter("Geometry", chapterNum++); - introSection = geomChapter.addSection("General Info", geomChapter.numberDepth() + 1); + introSection = geomChapter.addSection("General Info", geomChapter.getNumberDepth() + 1); writeMetadata(introSection, name, geom.getDescription(), userName, "Geometry"); - Section geomSection = geomChapter.addSection("Geometry", geomChapter.numberDepth() + 1); //title? + Section geomSection = geomChapter.addSection("Geometry", geomChapter.getNumberDepth() + 1); //title? writeGeom(geomSection, geom, null); document.add(geomChapter); document.close(); @@ -1128,14 +1125,6 @@ protected void writeHeaderFooter(String headerStr) throws DocumentException { } -/** - * writeHorizontalLine comment. - */ -protected void writeHorizontalLine() throws DocumentException { - // Default is no horizontal line... -} - - protected void writeJumpCondition(Section container, JumpCondition eq) throws DocumentException { Table eqTable = getTable(2, 100, 1, 2, 2); @@ -1198,12 +1187,12 @@ protected void writeMathDescAsImages(Section container, MathDescription mathDesc if (mathDesc == null) { return; } - Section mathDescSection = container.addSection("Math Description: " + mathDesc.getName(), container.depth() + 1); + Section mathDescSection = container.addSection("Math Description: " + mathDesc.getName(), container.getDepth() + 1); Section mathDescSubSection = null; Expression expArray [] = null; BufferedImage dummy = new BufferedImage(500, 50, BufferedImage.TYPE_3BYTE_BGR); int scale = 1; - int viewableWidth = (int)(document.getPageSize().width() - document.leftMargin() - document.rightMargin()); + int viewableWidth = (int)(document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin()); //add Constants Enumeration constantsList = mathDesc.getConstants(); while (constantsList.hasMoreElements()) { @@ -1222,11 +1211,11 @@ protected void writeMathDescAsImages(Section container, MathDescription mathDesc com.lowagie.text.Image expImage = com.lowagie.text.Image.getInstance(bufferedImage, null); expImage.setAlignment(com.lowagie.text.Image.ALIGN_LEFT); if (mathDescSubSection == null) { - mathDescSubSection = mathDescSection.addSection("Constants", mathDescSection.depth() + 1); + mathDescSubSection = mathDescSection.addSection("Constants", mathDescSection.getDepth() + 1); } - if (viewableWidth < Math.floor(expImage.scaledWidth())) { - expImage.scaleToFit(viewableWidth, expImage.plainHeight()); - System.out.println("Constant After scaling: " + expImage.scaledWidth()); + if (viewableWidth < Math.floor(expImage.getScaledWidth())) { + expImage.scaleToFit(viewableWidth, expImage.getPlainHeight()); + System.out.println("Constant After scaling: " + expImage.getScaledWidth()); } mathDescSubSection.add(expImage); } catch (Exception e) { @@ -1252,11 +1241,11 @@ protected void writeMathDescAsImages(Section container, MathDescription mathDesc com.lowagie.text.Image expImage = com.lowagie.text.Image.getInstance(bufferedImage, null); expImage.setAlignment(com.lowagie.text.Image.ALIGN_LEFT); if (mathDescSubSection == null) { - mathDescSubSection = mathDescSection.addSection("Functions", mathDescSection.depth() + 1); + mathDescSubSection = mathDescSection.addSection("Functions", mathDescSection.getDepth() + 1); } - if (viewableWidth < Math.floor(expImage.scaledWidth())) { - expImage.scaleToFit(viewableWidth, expImage.height()); - System.out.println("Function After scaling: " + expImage.scaledWidth()); + if (viewableWidth < Math.floor(expImage.getScaledWidth())) { + expImage.scaleToFit(viewableWidth, expImage.getHeight()); + System.out.println("Function After scaling: " + expImage.getScaledWidth()); } mathDescSubSection.add(expImage); } catch (Exception e) { @@ -1274,7 +1263,7 @@ protected void writeMathDescAsText(Section container, MathDescription mathDesc) if (mathDesc == null) { return; } - Section mathDescSection = container.addSection("Math Description: " + mathDesc.getName(), container.depth() + 1); + Section mathDescSection = container.addSection("Math Description: " + mathDesc.getName(), container.getDepth() + 1); Section mathDescSubSection = null; Table expTable = null; int widths [] = {2, 8}; @@ -1297,7 +1286,7 @@ protected void writeMathDescAsText(Section container, MathDescription mathDesc) } //expTable.setWidths(widths); breaks the contents of the cell, also, widths[1] = widths[1]/widths[0], widths[0] = 1 if (expTable != null) { - mathDescSubSection = mathDescSection.addSection("Constants", mathDescSection.depth() + 1); + mathDescSubSection = mathDescSection.addSection("Constants", mathDescSection.getDepth() + 1); mathDescSubSection.add(expTable); expTable = null; } @@ -1318,7 +1307,7 @@ protected void writeMathDescAsText(Section container, MathDescription mathDesc) expTable.addCell(createCell(exp.infix(), getFont())); } if (expTable != null) { - mathDescSubSection = mathDescSection.addSection("Functions", mathDescSection.depth() + 1); + mathDescSubSection = mathDescSection.addSection("Functions", mathDescSection.getDepth() + 1); mathDescSubSection.add(expTable); } writeSubDomainsEquationsAsText(mathDescSection, mathDesc); @@ -1357,7 +1346,7 @@ public void writeMathModel(MathModel mathModel, FileOutputStream fos, PageFormat if (preferences.includeMathDesc()) { MathDescription mathDesc = mathModel.getMathDescription(); Chapter mathDescChapter = new Chapter("Math Description", chapterNum++); - introSection = mathDescChapter.addSection("General Info", mathDescChapter.numberDepth() + 1); + introSection = mathDescChapter.addSection("General Info", mathDescChapter.getNumberDepth() + 1); writeMetadata(introSection, name, mathModel.getDescription(), userName, "MathModel"); writeMathDescAsText(mathDescChapter, mathDesc); document.add(mathDescChapter); @@ -1367,7 +1356,7 @@ public void writeMathModel(MathModel mathModel, FileOutputStream fos, PageFormat if (sims != null) { Chapter simChapter = new Chapter("Simulations", chapterNum++); if (introSection == null) { - introSection = simChapter.addSection("General Info", simChapter.numberDepth() + 1); + introSection = simChapter.addSection("General Info", simChapter.getNumberDepth() + 1); writeMetadata(introSection, name, mathModel.getDescription(), userName, "MathModel"); } for (int i = 0; i < sims.length; i++) { @@ -1383,7 +1372,7 @@ public void writeMathModel(MathModel mathModel, FileOutputStream fos, PageFormat if (geom != null) { Chapter geomChapter = new Chapter("Geometry", chapterNum++); if (introSection == null) { - introSection = geomChapter.addSection("General Info", geomChapter.numberDepth() + 1); + introSection = geomChapter.addSection("General Info", geomChapter.getNumberDepth() + 1); writeMetadata(introSection, name, mathModel.getDescription(), userName, "MathModel"); } writeGeom(geomChapter, geom, null); @@ -1450,7 +1439,7 @@ protected void writeMembraneMapping(Section simContextSection, SimulationContext memMapTable.addCell(createCell(spCap, getFont())); } if (memMapTable != null) { - memMapSection = simContextSection.addSection("Membrane Mapping For " + simContext.getName(), simContextSection.numberDepth() + 1); + memMapSection = simContextSection.addSection("Membrane Mapping For " + simContext.getName(), simContextSection.getNumberDepth() + 1); memMapSection.add(memMapTable); } int[] widths = {1, 1, 1, 5, 8}; @@ -1596,7 +1585,7 @@ protected void writeModel(Chapter physioChapter, Model model) throws DocumentExc // if (model.getNumStructures() > 0) { // try { // ByteArrayOutputStream bos = generateDocStructureImage(model, ITextWriter.LOW_RESOLUTION); -// structSection = physioChapter.addSection("Structures For: " + model.getName(), physioChapter.numberDepth() + 1); +// structSection = physioChapter.addSection("Structures For: " + model.getName(), physioChapter.getNumberDepth() + 1); // addImage(structSection, bos); // } catch(Exception e) { // System.err.println("Unable to add structures image for model: " + model.getName()); @@ -1769,7 +1758,7 @@ protected void writeReactionContext(Section simContextSection, SimulationContext reactionSpecTable.addCell(createCell((reactionSpecs[i].isFast() ? " T ": " F "), getFont())); } if (reactionSpecTable != null) { - rcSection = simContextSection.addSection("Reaction Mapping For " + simContext.getName(), simContextSection.numberDepth() + 1); + rcSection = simContextSection.addSection("Reaction Mapping For " + simContext.getName(), simContextSection.getNumberDepth() + 1); rcSection.add(reactionSpecTable); } @@ -1803,7 +1792,7 @@ protected void writeReactionContext(Section simContextSection, SimulationContext } if (speciesSpecTable != null) { if (rcSection == null) { - rcSection = simContextSection.addSection("Reaction Mapping For " + simContext.getName(), simContextSection.numberDepth() + 1); + rcSection = simContextSection.addSection("Reaction Mapping For " + simContext.getName(), simContextSection.getNumberDepth() + 1); } speciesSpecTable.setWidths(widths); rcSection.add(speciesSpecTable); @@ -1861,7 +1850,7 @@ protected void writeReactions(Chapter physioChapter, Model model) throws Documen } Paragraph reactionParagraph = new Paragraph(); reactionParagraph.add(new Chunk("Structures and Reactions Diagram").setLocalDestination(model.getName())); - Section reactionDiagramSection = physioChapter.addSection(reactionParagraph, physioChapter.numberDepth() + 1); + Section reactionDiagramSection = physioChapter.addSection(reactionParagraph, physioChapter.getNumberDepth() + 1); try{ addImage(reactionDiagramSection, encodeJPEG(generateDocReactionsImage(model,null))); }catch(Exception e){ @@ -1882,7 +1871,7 @@ protected void writeReactions(Chapter physioChapter, Model model) throws Documen if (firstTime) { Paragraph linkParagraph = new Paragraph(); linkParagraph.add(new Chunk("Reaction(s) in " + model.getStructure(i).getName()).setLocalDestination(model.getStructure(i).getName())); - reactStructSection = physioChapter.addSection(linkParagraph, physioChapter.numberDepth() + 1); + reactStructSection = physioChapter.addSection(linkParagraph, physioChapter.getNumberDepth() + 1); firstTime = false; } rs = reactionSteps[j]; @@ -1952,7 +1941,7 @@ protected void writeReactions(Chapter physioChapter, Model model) throws Documen modifiersVector.removeAllElements(); } - Section reactionSection = reactStructSection.addSection(type + " " + rs.getName(), reactStructSection.numberDepth() + 1); + Section reactionSection = reactStructSection.addSection(type + " " + rs.getName(), reactStructSection.getNumberDepth() + 1); //Annotation VCMetaData vcMetaData = rs.getModel().getVcMetaData(); if (vcMetaData.getFreeTextAnnotation(rs) != null) { @@ -1985,7 +1974,7 @@ protected void writeSimulation(Section container, Simulation sim) throws Documen if (sim == null) { return; } - Section simSection = container.addSection(sim.getName(), container.numberDepth() + 1); + Section simSection = container.addSection(sim.getName(), container.getNumberDepth() + 1); writeMetadata(simSection, sim.getName(), sim.getDescription(), null, "Simulation "); //add overriden params Table overParamTable = null; @@ -2101,7 +2090,7 @@ protected void writeSimulation(Section container, Simulation sim) throws Documen //Electrical Stimulus: ignored the Ground Electrode, protected void writeSimulationContext(Chapter simContextsChapter, SimulationContext simContext, PublishPreferences preferences) throws Exception { - Section simContextSection = simContextsChapter.addSection("Application: " + simContext.getName(), simContextsChapter.numberDepth() + 1); + Section simContextSection = simContextsChapter.addSection("Application: " + simContext.getName(), simContextsChapter.getNumberDepth() + 1); writeMetadata(simContextSection, simContext.getName(), simContext.getDescription(), null, "Application "); //add geometry context (structure mapping) writeStructureMapping(simContextSection, simContext); @@ -2118,7 +2107,7 @@ protected void writeSimulationContext(Chapter simContextsChapter, SimulationCont //writeMathDescAsImages(simContextSection, simContext.getMathDescription()); } if (preferences.includeSim()) { - Section simSection = simContextSection.addSection("Simulation(s)", simContextSection.depth() + 1); + Section simSection = simContextSection.addSection("Simulation(s)", simContextSection.getDepth() + 1); Simulation sims [] = simContext.getSimulations(); for (int i = 0; i < sims.length; i++) { writeSimulation(simSection, sims[i]); @@ -2208,7 +2197,7 @@ protected void writeStructureMapping(Section simContextSection, SimulationContex /*try { ByteArrayOutputStream bos = generateStructureMappingImage(sc); com.lowagie.text.Image structMapImage = com.lowagie.text.Image.getInstance(Toolkit.getDefaultToolkit().createImage(bos.toByteArray()), null); - structMappSection = simContextSection.addSection("Structure Mapping For " + sc.getName(), simContextSection.numberDepth() + 1); + structMappSection = simContextSection.addSection("Structure Mapping For " + sc.getName(), simContextSection.getNumberDepth() + 1); structMappSection.add(structMapImage); } catch (Exception e) { System.err.println("Unable to add structure mapping image to report."); @@ -2272,7 +2261,7 @@ protected void writeStructureMapping(Section simContextSection, SimulationContex } if (structMapTable != null) { if (structMappSection == null) { - structMappSection = simContextSection.addSection("Structure Mapping For " + sc.getName(), simContextSection.numberDepth() + 1); + structMappSection = simContextSection.addSection("Structure Mapping For " + sc.getName(), simContextSection.getNumberDepth() + 1); } structMappSection.add(structMapTable); } @@ -2284,10 +2273,10 @@ protected void writeSubDomainsEquationsAsImages(Section mathDescSection, MathDes Enumeration subDomains = mathDesc.getSubDomains(); Expression expArray[]; - Section volDomains = mathDescSection.addSection("Volume Domains", mathDescSection.depth() + 1); - Section memDomains = mathDescSection.addSection("Membrane Domains", mathDescSection.depth() + 1); + Section volDomains = mathDescSection.addSection("Volume Domains", mathDescSection.getDepth() + 1); + Section memDomains = mathDescSection.addSection("Membrane Domains", mathDescSection.getDepth() + 1); int scale = 1, height = 200; //arbitrary - int viewableWidth = (int)(document.getPageSize().width() - document.leftMargin() - document.rightMargin()); + int viewableWidth = (int)(document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin()); BufferedImage dummy = new BufferedImage(viewableWidth, height, BufferedImage.TYPE_3BYTE_BGR); while(subDomains.hasMoreElements()) { SubDomain subDomain = subDomains.nextElement(); @@ -2309,9 +2298,9 @@ protected void writeSubDomainsEquationsAsImages(Section mathDescSection, MathDes expArray = (Expression [])expList.toArray(new Expression[expList.size()]); Section tempSection = null; if (subDomain instanceof CompartmentSubDomain) { - tempSection = volDomains.addSection(subDomain.getName(), volDomains.depth() + 1); + tempSection = volDomains.addSection(subDomain.getName(), volDomains.getDepth() + 1); } else if (subDomain instanceof MembraneSubDomain) { - tempSection = memDomains.addSection(subDomain.getName(), memDomains.depth() + 1); + tempSection = memDomains.addSection(subDomain.getName(), memDomains.getDepth() + 1); } try { Dimension dim = ExpressionCanvas.getExpressionImageSize(expArray, (Graphics2D)dummy.getGraphics()); @@ -2321,9 +2310,9 @@ protected void writeSubDomainsEquationsAsImages(Section mathDescSection, MathDes //Table imageTable = null;; com.lowagie.text.Image expImage = com.lowagie.text.Image.getInstance(bufferedImage, null); expImage.setAlignment(com.lowagie.text.Image.LEFT); - if (viewableWidth < expImage.scaledWidth()) { - expImage.scaleToFit(viewableWidth, expImage.height()); - System.out.println("SubDomain expresions After scaling: " + expImage.scaledWidth()); + if (viewableWidth < expImage.getScaledWidth()) { + expImage.scaleToFit(viewableWidth, expImage.getHeight()); + System.out.println("SubDomain expresions After scaling: " + expImage.getScaledWidth()); } /*Cell imageCell = new Cell(); imageCell.add(expImage); @@ -2352,18 +2341,18 @@ protected void writeSubDomainsEquationsAsImages(Section mathDescSection, MathDes protected void writeSubDomainsEquationsAsText(Section mathDescSection, MathDescription mathDesc) throws DocumentException { Enumeration subDomains = mathDesc.getSubDomains(); - Section volDomains = mathDescSection.addSection("Volume Domains", mathDescSection.depth() + 1); - Section memDomains = mathDescSection.addSection("Membrane Domains", mathDescSection.depth() + 1); - Section filDomains = mathDescSection.addSection("Filament Domains", mathDescSection.depth() + 1); + Section volDomains = mathDescSection.addSection("Volume Domains", mathDescSection.getDepth() + 1); + Section memDomains = mathDescSection.addSection("Membrane Domains", mathDescSection.getDepth() + 1); + Section filDomains = mathDescSection.addSection("Filament Domains", mathDescSection.getDepth() + 1); while(subDomains.hasMoreElements()) { Section tempSection = null; SubDomain subDomain = subDomains.nextElement(); if (subDomain instanceof CompartmentSubDomain) { - tempSection = volDomains.addSection(subDomain.getName(), volDomains.depth() + 1); + tempSection = volDomains.addSection(subDomain.getName(), volDomains.getDepth() + 1); } else if (subDomain instanceof MembraneSubDomain) { - tempSection = memDomains.addSection(subDomain.getName(), memDomains.depth() + 1); + tempSection = memDomains.addSection(subDomain.getName(), memDomains.getDepth() + 1); } else if (subDomain instanceof FilamentSubDomain) { - tempSection = filDomains.addSection(subDomain.getName(), filDomains.depth() + 1); + tempSection = filDomains.addSection(subDomain.getName(), filDomains.getDepth() + 1); } Enumeration equationsList = subDomain.getEquations(); while (equationsList.hasMoreElements()) { diff --git a/vcell-core/src/main/java/cbit/vcell/publish/PDFWriter.java b/vcell-core/src/main/java/cbit/vcell/publish/PDFWriter.java index 5a7a6a21db..2391888fe4 100644 --- a/vcell-core/src/main/java/cbit/vcell/publish/PDFWriter.java +++ b/vcell-core/src/main/java/cbit/vcell/publish/PDFWriter.java @@ -9,17 +9,12 @@ */ package cbit.vcell.publish; -import java.io.FileOutputStream; -import com.lowagie.text.DocWriter; -import com.lowagie.text.DocumentException; -import com.lowagie.text.Graphic; -import com.lowagie.text.HeaderFooter; -import com.lowagie.text.Paragraph; -import com.lowagie.text.Phrase; -import com.lowagie.text.Rectangle; +import com.lowagie.text.*; import com.lowagie.text.pdf.PdfWriter; +import java.io.FileOutputStream; + /** * Note: Two incomplete methods for publishing an awt component were deleted (writeComponent(), writeSnapshot()). * Creation date: (4/18/2003 2:16:53 PM) @@ -59,15 +54,4 @@ protected void writeHeaderFooter(String headerStr) throws DocumentException { document.setFooter(headerFooter); } - -/** - * PDF implementation. - */ -protected void writeHorizontalLine() throws DocumentException { - document.add(new Paragraph("\n")); - Graphic graphic = new Graphic(); - graphic.setHorizontalLine(1, 75); - document.add(graphic); - document.add(new Paragraph("\n")); -} } diff --git a/vcell-core/src/main/java/cbit/vcell/publish/RTFWriter.java b/vcell-core/src/main/java/cbit/vcell/publish/RTFWriter.java deleted file mode 100644 index 3c98ae1cc3..0000000000 --- a/vcell-core/src/main/java/cbit/vcell/publish/RTFWriter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 1999-2011 University of Connecticut Health Center - * - * Licensed under the MIT License (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.opensource.org/licenses/mit-license.php - */ - -package cbit.vcell.publish; -import java.io.FileOutputStream; - -import com.lowagie.text.DocWriter; -import com.lowagie.text.DocumentException; -import com.lowagie.text.HeaderFooter; -import com.lowagie.text.Phrase; -import com.lowagie.text.rtf.RtfWriter; - -/** - * RTF implementation. - * Creation date: (4/18/2003 4:18:38 PM) - * @author: John Wagner - */ -public class RTFWriter extends ITextWriter { - -public RTFWriter() { - super(); -} - - -public DocWriter createDocWriter(FileOutputStream fileOutputStream) throws DocumentException { - return RtfWriter.getInstance(this.document, fileOutputStream); -} - - -protected void writeHeaderFooter(String headerStr) throws DocumentException { - - if (headerStr.trim().length() > 0) { - HeaderFooter headerFooter = new HeaderFooter(new Phrase(headerStr), false); - document.setHeader(headerFooter); - } - HeaderFooter headerFooter = new HeaderFooter(new Phrase("Page "), true); - document.setFooter(headerFooter); -} -} diff --git a/vcell-core/src/main/java/org/vcell/util/document/User.java b/vcell-core/src/main/java/org/vcell/util/document/User.java index 1538404da0..89466e9c12 100644 --- a/vcell-core/src/main/java/org/vcell/util/document/User.java +++ b/vcell-core/src/main/java/org/vcell/util/document/User.java @@ -51,8 +51,6 @@ public String toDatabaseString(){ private KeyValue key = null; public static final String VCellTestAccountName = "vcelltestaccount"; - public static final String[] publishers = {"frm","schaff","ion"}; - public static final User tempUser = new User("temp",new KeyValue("123")); public static final String VCELL_GUEST = "vcellguest"; @@ -81,6 +79,15 @@ public SpecialUser(String userid, KeyValue key,SPECIAL_CLAIM[] mySpecials) { public SPECIAL_CLAIM[] getMySpecials() { return mySpecials; } + + public boolean isAdmin() { + return Arrays.asList(mySpecials).contains(SPECIAL_CLAIM.admins); + } + + public boolean isPublisher() { + return Arrays.asList(mySpecials).contains(SPECIAL_CLAIM.publicationEditors); + } + // @Override // public boolean compareEqual(Matchable obj) { // // TODO Auto-generated method stub @@ -161,15 +168,6 @@ public int hashCode() { return getName().hashCode(); } -/** - * Insert the method's description here. - * Creation date: (5/23/2006 8:33:53 AM) - * @return boolean - */ -public boolean isPublisher() { - return Arrays.asList(publishers).contains(userName); -} - /** * @return true if this is test account diff --git a/vcell-core/src/test/java/org/vcell/util/document/UserTest.java b/vcell-core/src/test/java/org/vcell/util/document/UserTest.java index f52f9060c3..8989d0dec5 100644 --- a/vcell-core/src/test/java/org/vcell/util/document/UserTest.java +++ b/vcell-core/src/test/java/org/vcell/util/document/UserTest.java @@ -13,9 +13,9 @@ public class UserTest { @Test public void publisherTest( ) { - User u = new User("schaff", testKey( )); + User.SpecialUser u = new User.SpecialUser("schaff", testKey( ), new User.SPECIAL_CLAIM[] {User.SPECIAL_CLAIM.publicationEditors}); Assertions.assertTrue(u.isPublisher()); - u = new User("fido", testKey( )); + u = new User.SpecialUser("fido", testKey( ), new User.SPECIAL_CLAIM[]{}); assertFalse(u.isPublisher()); } @Test diff --git a/vcell-rest/pom.xml b/vcell-rest/pom.xml index e6d4874453..a320b8fb13 100644 --- a/vcell-rest/pom.xml +++ b/vcell-rest/pom.xml @@ -97,6 +97,11 @@ quarkus-junit5 test + + io.quarkiverse.itext + quarkus-itext + 3.0.5 + com.nimbusds oauth2-oidc-sdk diff --git a/vcell-rest/src/main/java/org/vcell/restq/db/AdminRestDB.java b/vcell-rest/src/main/java/org/vcell/restq/db/AdminRestDB.java new file mode 100644 index 0000000000..95e81b3bc5 --- /dev/null +++ b/vcell-rest/src/main/java/org/vcell/restq/db/AdminRestDB.java @@ -0,0 +1,35 @@ +package org.vcell.restq.db; + +import cbit.vcell.modeldb.AdminDBTopLevel; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; +import org.vcell.util.DataAccessException; +import org.vcell.util.document.User; + +import java.sql.SQLException; + +@ApplicationScoped +public class AdminRestDB { + + private final AdminDBTopLevel adminDBTopLevel; + + @Inject + public AdminRestDB(AgroalConnectionFactory agroalConnectionFactory) throws DataAccessException { + try { + adminDBTopLevel = new AdminDBTopLevel(agroalConnectionFactory); + } catch (SQLException e) { + throw new DataAccessException("database error during initialization", e); + } + } + + public String getUsageSummaryHtml(User vcUser) throws DataAccessException, SQLException { + User.SpecialUser specialUser = adminDBTopLevel.getUser(vcUser.getName(), true); + if (specialUser.isAdmin()){ + return adminDBTopLevel.getBasicStatistics(); + }else{ + throw new WebApplicationException("not authorized", 403); + } + } + +} diff --git a/vcell-rest/src/main/java/org/vcell/restq/handlers/AdminResource.java b/vcell-rest/src/main/java/org/vcell/restq/handlers/AdminResource.java new file mode 100644 index 0000000000..9fab22e8f5 --- /dev/null +++ b/vcell-rest/src/main/java/org/vcell/restq/handlers/AdminResource.java @@ -0,0 +1,94 @@ +package org.vcell.restq.handlers; + +import com.lowagie.text.Document; +import com.lowagie.text.html.simpleparser.HTMLWorker; +import com.lowagie.text.pdf.PdfWriter; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.security.RolesAllowed; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.jboss.resteasy.reactive.NoCache; +import org.vcell.restq.db.AdminRestDB; +import org.vcell.restq.db.UserRestDB; +import org.vcell.util.DataAccessException; +import org.vcell.util.PermissionException; +import org.vcell.util.document.User; + +import java.io.StringReader; +import java.sql.SQLException; + +@Path("/api/v1/admin") +@RequestScoped +public class AdminResource { + + // get Quarkus logger + private static final Logger lg = LogManager.getLogger(AdminResource.class); + + @Inject + SecurityIdentity securityIdentity; + + private final AdminRestDB adminRestDB; + private final UserRestDB userRestDB; + + @Inject + public AdminResource(AdminRestDB adminRestDB, UserRestDB userRestDB) { + this.adminRestDB = adminRestDB; + this.userRestDB = userRestDB; + } + + @GET + @Path("/usage") + @RolesAllowed("admin") + @Operation(operationId = "getUsage", summary = "Get usage summary") + @NoCache + @APIResponse( + responseCode = "200", + description = "The PDF report", + content = @Content(mediaType = "application/pdf", schema = @Schema(type = SchemaType.STRING, format = "binary")) + ) + public Response getUsage() throws DataAccessException { + if (securityIdentity.isAnonymous()){ + throw new WebApplicationException("not authenticated", 401); + } + User vcellUser = userRestDB.getUserFromIdentity(securityIdentity); + if (vcellUser == null) { + throw new WebApplicationException("vcell user not specified", 401); + } + try { + String htmlString = adminRestDB.getUsageSummaryHtml(vcellUser); + StreamingOutput fileStream = output -> { + try { + Document document = new Document(); + PdfWriter writer = PdfWriter.getInstance(document, output); + document.open(); + HTMLWorker htmlWorker = new HTMLWorker(document); + htmlWorker.parse(new StringReader(htmlString)); + document.close(); + } catch (Exception e) { + throw new WebApplicationException("Error while generating PDF", e); + } + }; + return Response + .ok(fileStream, "application/pdf") + .header("content-disposition","attachment; filename = usage_summary.pdf") + .build(); + } catch (PermissionException e) { + throw new WebApplicationException("not authorized", 403); + } catch (SQLException e) { + lg.error("database error", e); + throw new DataAccessException("database error", e); + } + } +} diff --git a/vcell-rest/src/main/resources/application.properties b/vcell-rest/src/main/resources/application.properties index 17cc5e5e11..f30a0b980b 100644 --- a/vcell-rest/src/main/resources/application.properties +++ b/vcell-rest/src/main/resources/application.properties @@ -1,6 +1,6 @@ quarkus.http.port=9000 quarkus.http.cors=true -%prod.quarkus.http.cors.origins=http://vcellapi-test.cam.uchc.edu,https://vcellapi-test.cam.uchc.edu +%prod.quarkus.http.cors.origins=http://vcellapi-test.cam.uchc.edu,https://vcellapi-test.cam.uchc.edu,http://localhost:4200 %prod.quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS %dev.quarkus.http.cors.origins=/.*/ %dev.quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS @@ -52,7 +52,7 @@ quarkus.datasource.postgresql.db-kind=postgresql %test.quarkus.datasource.postgresql.jdbc.acquisition-timeout=120S %test.quarkus.keycloak.devservices.users.alice=alice -%test.quarkus.keycloak.devservices.roles.alice=user,admin,curator,owner +%test.quarkus.keycloak.devservices.roles.alice=user,curator,owner,admin %test.quarkus.keycloak.devservices.users.bob=bob %test.quarkus.keycloak.devservices.roles.bob=user diff --git a/vcell-rest/src/main/resources/scripts/init.sql b/vcell-rest/src/main/resources/scripts/init.sql index 18c6d5b1eb..88e02e2392 100644 --- a/vcell-rest/src/main/resources/scripts/init.sql +++ b/vcell-rest/src/main/resources/scripts/init.sql @@ -97,4 +97,6 @@ INSERT INTO vc_group VALUES (6,0,0,0,0); INSERT INTO vc_available VALUES (7,current_timestamp,'true',NULL,NULL); INSERT INTO vc_apiclient VALUES (8,'defaultApiClient','85133f8d-26f7-4247-8356-d175399fc2e6','98d000d6-adff-4f8f-a00e-6c28dbd8c571'); INSERT INTO vc_useridentity VALUES ( 9, 2,'auth0|65cb6311365d79c2fb96a005', 'https://dev-dzhx7i2db3x3kkvq.us.auth0.com/', CURRENT_TIMESTAMP); -INSERT INTO vc_specialusers VALUES ( 10,'publication',2,NULL); +INSERT INTO vc_specialusers VALUES ( 10,'publicationEditors',2,NULL); +INSERT INTO vc_specialusers VALUES ( 11,'powerUsers',2,NULL); +INSERT INTO vc_specialusers VALUES ( 12,'admins',2,NULL); diff --git a/vcell-rest/src/test/java/org/vcell/restq/AdminResourceTest.java b/vcell-rest/src/test/java/org/vcell/restq/AdminResourceTest.java new file mode 100644 index 0000000000..a95fc0d774 --- /dev/null +++ b/vcell-rest/src/test/java/org/vcell/restq/AdminResourceTest.java @@ -0,0 +1,70 @@ +package org.vcell.restq; + +import cbit.vcell.resource.PropertyLoader; +import cbit.vcell.xml.XmlParseException; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.restassured.response.Response; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.*; +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.api.UsersResourceApi; +import org.vcell.restq.config.CDIVCellConfigProvider; +import org.vcell.restq.db.AgroalConnectionFactory; +import org.vcell.util.DataAccessException; + +import java.beans.PropertyVetoException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.SQLException; + +import static io.restassured.RestAssured.given; + +@QuarkusTest +public class AdminResourceTest { + @ConfigProperty(name = "quarkus.http.test-port") + Integer testPort; + + KeycloakTestClient keycloakClient = new KeycloakTestClient(); + @Inject + AgroalConnectionFactory agroalConnectionFactory; + + private ApiClient aliceAPIClient; + + @BeforeAll + public static void setupConfig(){ + PropertyLoader.setConfigProvider(new CDIVCellConfigProvider()); + } + + @BeforeEach + public void createClients(){ + aliceAPIClient = TestEndpointUtils.createAuthenticatedAPIClient(keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.alice); + } + + @AfterEach + public void removeOIDCMappings() throws SQLException, DataAccessException { + TestEndpointUtils.removeAllMappings(agroalConnectionFactory); + } + + // TODO: Right now the biomodel endpoint doesn't implement authentication, but when it does it'll need to + @Test + public void testGetUsage() throws IOException, ApiException, XmlParseException, PropertyVetoException { + + boolean mapped = new UsersResourceApi(aliceAPIClient).mapUser(TestEndpointUtils.administratorUserLoginInfo); + Assertions.assertTrue(mapped); + + Response response = given() + .auth().oauth2(keycloakClient.getAccessToken(TestEndpointUtils.TestOIDCUsers.alice.name())) + .when().get("/api/v1/admin/usage") + .then().extract().response(); + + Assertions.assertEquals(200, response.getStatusCode()); + Assertions.assertEquals("application/pdf", response.getContentType()); + + byte[] pdfBytes = response.asByteArray(); + Assertions.assertTrue(pdfBytes.length > 0); + } +} diff --git a/vcell-rest/src/test/java/org/vcell/restq/apiclient/AdminApiTest.java b/vcell-rest/src/test/java/org/vcell/restq/apiclient/AdminApiTest.java new file mode 100644 index 0000000000..a3d4cb4157 --- /dev/null +++ b/vcell-rest/src/test/java/org/vcell/restq/apiclient/AdminApiTest.java @@ -0,0 +1,115 @@ +package org.vcell.restq.apiclient; + +import cbit.vcell.resource.PropertyLoader; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.*; +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.api.AdminResourceApi; +import org.vcell.restclient.api.UsersResourceApi; +import org.vcell.restq.TestEndpointUtils; +import org.vcell.restq.config.CDIVCellConfigProvider; +import org.vcell.restq.db.AgroalConnectionFactory; +import org.vcell.util.DataAccessException; + +import java.io.File; +import java.sql.SQLException; + +@QuarkusTest +public class AdminApiTest { + + @ConfigProperty(name = "quarkus.oidc.auth-server-url") + String authServerUrl; + + @ConfigProperty(name = "quarkus.http.test-port") + Integer testPort; + + @Inject + AgroalConnectionFactory agroalConnectionFactory; + + KeycloakTestClient keycloakClient = new KeycloakTestClient(); + + private ApiClient aliceAPIClient; + private ApiClient bobAPIClient; + + @BeforeAll + public static void setupConfig(){ + PropertyLoader.setConfigProvider(new CDIVCellConfigProvider()); + } + + @BeforeEach + public void createClients(){ + aliceAPIClient = TestEndpointUtils.createAuthenticatedAPIClient(keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.alice); + bobAPIClient = TestEndpointUtils.createAuthenticatedAPIClient(keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.bob); + } + + @AfterEach + public void removeOIDCMappings() throws SQLException, DataAccessException { + TestEndpointUtils.removeAllMappings(agroalConnectionFactory); + } + + @Test + public void testUsageReport_AdminRole_AdminUser() throws ApiException { + // alice has admin role, mapped to an admin user - should succeed + boolean mapped = new UsersResourceApi(aliceAPIClient).mapUser(TestEndpointUtils.administratorUserLoginInfo); + Assertions.assertTrue(mapped); + File usageSummaryFile = new AdminResourceApi(aliceAPIClient).getUsage(); + Assertions.assertTrue(usageSummaryFile.exists()); + Assertions.assertTrue(usageSummaryFile.isFile()); + Assertions.assertTrue(usageSummaryFile.length() > 0); + } + + @Test + public void testUsageReport_AdminRole_NonadminUser() throws ApiException { + // alice has an admin role, but is mapped to a non-admin user - should fail + boolean mapped = new UsersResourceApi(aliceAPIClient).mapUser(TestEndpointUtils.vcellNagiosUserLoginInfo); + Assertions.assertTrue(mapped); + ApiException apiException = Assertions.assertThrows(ApiException.class, () -> { + new AdminResourceApi(aliceAPIClient).getUsage(); + }); + Assertions.assertEquals(403, apiException.getCode()); + } + + @Test + public void testUsageReport_AdminRole_NotMapped() throws ApiException { + // alice has admin role, but is not mapped any user - should fail + ApiException apiException = Assertions.assertThrows(ApiException.class, () -> { + new AdminResourceApi(aliceAPIClient).getUsage(); + }); + Assertions.assertEquals(401, apiException.getCode()); + } + + @Test + public void testUsageReport_NonadminRole_AdminUser() throws ApiException { + // bob does not have admin role, mapped to admin user - should fail + boolean mapped = new UsersResourceApi(bobAPIClient).mapUser(TestEndpointUtils.administratorUserLoginInfo); + Assertions.assertTrue(mapped); + ApiException apiException = Assertions.assertThrows(ApiException.class, () -> { + new AdminResourceApi(aliceAPIClient).getUsage(); + }); + Assertions.assertEquals(401, apiException.getCode()); + } + + @Test + public void testUsageReport_NonadminRole_NonadminUser() throws ApiException { + // bob is not an admin, mapped to a non-admin user - should fail + boolean mapped = new UsersResourceApi(bobAPIClient).mapUser(TestEndpointUtils.vcellNagiosUserLoginInfo); + Assertions.assertTrue(mapped); + ApiException apiException = Assertions.assertThrows(ApiException.class, () -> { + new AdminResourceApi(aliceAPIClient).getUsage(); + }); + Assertions.assertEquals(401, apiException.getCode()); + } + + @Test + public void testUsageReport_NonadminRole_NotMapped() throws ApiException { + // bob is not an admin, and is not mapped to any user - should fail + ApiException apiException = Assertions.assertThrows(ApiException.class, () -> { + new AdminResourceApi(aliceAPIClient).getUsage(); + }); + Assertions.assertEquals(401, apiException.getCode()); + } +} diff --git a/vcell-restclient/.openapi-generator/FILES b/vcell-restclient/.openapi-generator/FILES index ca79e9c4b6..f71091c7fc 100644 --- a/vcell-restclient/.openapi-generator/FILES +++ b/vcell-restclient/.openapi-generator/FILES @@ -2,6 +2,7 @@ README.md api/openapi.yaml docs/AccesTokenRepresentationRecord.md +docs/AdminResourceApi.md docs/BioModel.md docs/BioModelResourceApi.md docs/BiomodelRef.md @@ -26,6 +27,7 @@ src/main/java/org/vcell/restclient/Pair.java src/main/java/org/vcell/restclient/RFC3339DateFormat.java src/main/java/org/vcell/restclient/ServerConfiguration.java src/main/java/org/vcell/restclient/ServerVariable.java +src/main/java/org/vcell/restclient/api/AdminResourceApi.java src/main/java/org/vcell/restclient/api/BioModelResourceApi.java src/main/java/org/vcell/restclient/api/HelloWorldApi.java src/main/java/org/vcell/restclient/api/PublicationResourceApi.java diff --git a/vcell-restclient/README.md b/vcell-restclient/README.md index 2063d97cf8..c60c2c86d6 100644 --- a/vcell-restclient/README.md +++ b/vcell-restclient/README.md @@ -75,20 +75,20 @@ Please follow the [installation](#installation) instruction and execute the foll import org.vcell.restclient.*; import org.vcell.restclient.model.*; -import org.vcell.restclient.api.BioModelResourceApi; +import org.vcell.restclient.api.AdminResourceApi; -public class BioModelResourceApiExample { +public class AdminResourceApiExample { public static void main(String[] args) { ApiClient defaultClient = Configuration.getDefaultApiClient(); // Configure clients using the `defaultClient` object, such as // overriding the host and port, timeout, etc. - BioModelResourceApi apiInstance = new BioModelResourceApi(defaultClient); - String bioModelID = "bioModelID_example"; // String | + AdminResourceApi apiInstance = new AdminResourceApi(defaultClient); try { - apiInstance.deleteBioModel(bioModelID); + File result = apiInstance.getUsage(); + System.out.println(result); } catch (ApiException e) { - System.err.println("Exception when calling BioModelResourceApi#deleteBioModel"); + System.err.println("Exception when calling AdminResourceApi#getUsage"); System.err.println("Status code: " + e.getCode()); System.err.println("Reason: " + e.getResponseBody()); System.err.println("Response headers: " + e.getResponseHeaders()); @@ -105,6 +105,8 @@ All URIs are relative to *https://vcellapi-test.cam.uchc.edu* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- +*AdminResourceApi* | [**getUsage**](docs/AdminResourceApi.md#getUsage) | **GET** /api/v1/admin/usage | Get usage summary +*AdminResourceApi* | [**getUsageWithHttpInfo**](docs/AdminResourceApi.md#getUsageWithHttpInfo) | **GET** /api/v1/admin/usage | Get usage summary *BioModelResourceApi* | [**deleteBioModel**](docs/BioModelResourceApi.md#deleteBioModel) | **DELETE** /api/v1/bioModel/{bioModelID} | Delete the BioModel from VCell's database. *BioModelResourceApi* | [**deleteBioModelWithHttpInfo**](docs/BioModelResourceApi.md#deleteBioModelWithHttpInfo) | **DELETE** /api/v1/bioModel/{bioModelID} | Delete the BioModel from VCell's database. *BioModelResourceApi* | [**getBiomodelById**](docs/BioModelResourceApi.md#getBiomodelById) | **GET** /api/v1/bioModel/{bioModelID} | Get BioModel information in JSON format by ID. diff --git a/vcell-restclient/api/openapi.yaml b/vcell-restclient/api/openapi.yaml index 629a256003..17d3fc3cc6 100644 --- a/vcell-restclient/api/openapi.yaml +++ b/vcell-restclient/api/openapi.yaml @@ -14,6 +14,28 @@ info: servers: - url: https://vcellapi-test.cam.uchc.edu paths: + /api/v1/admin/usage: + get: + operationId: getUsage + responses: + "200": + content: + application/pdf: + schema: + format: binary + type: string + description: The PDF report + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - openId: + - admin + summary: Get usage summary + tags: + - Admin Resource + x-accepts: application/pdf /api/v1/bioModel/upload_bioModel: post: operationId: uploadBioModel @@ -229,7 +251,7 @@ paths: responses: "200": content: - application/json: + text/plain: schema: type: boolean description: OK @@ -244,7 +266,7 @@ paths: tags: - Users Resource x-content-type: application/json - x-accepts: application/json + x-accepts: text/plain /api/v1/users/mappedUser: get: operationId: getMappedUser diff --git a/vcell-restclient/docs/AdminResourceApi.md b/vcell-restclient/docs/AdminResourceApi.md new file mode 100644 index 0000000000..cdbb1fd29b --- /dev/null +++ b/vcell-restclient/docs/AdminResourceApi.md @@ -0,0 +1,140 @@ +# AdminResourceApi + +All URIs are relative to *https://vcellapi-test.cam.uchc.edu* + +| Method | HTTP request | Description | +|------------- | ------------- | -------------| +| [**getUsage**](AdminResourceApi.md#getUsage) | **GET** /api/v1/admin/usage | Get usage summary | +| [**getUsageWithHttpInfo**](AdminResourceApi.md#getUsageWithHttpInfo) | **GET** /api/v1/admin/usage | Get usage summary | + + + +## getUsage + +> File getUsage() + +Get usage summary + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.AdminResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcellapi-test.cam.uchc.edu"); + + + AdminResourceApi apiInstance = new AdminResourceApi(defaultClient); + try { + File result = apiInstance.getUsage(); + System.out.println(result); + } catch (ApiException e) { + System.err.println("Exception when calling AdminResourceApi#getUsage"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**File**](File.md) + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/pdf + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | The PDF report | - | +| **401** | Not Authorized | - | +| **403** | Not Allowed | - | + +## getUsageWithHttpInfo + +> ApiResponse getUsage getUsageWithHttpInfo() + +Get usage summary + +### Example + +```java +// Import classes: +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.ApiResponse; +import org.vcell.restclient.Configuration; +import org.vcell.restclient.auth.*; +import org.vcell.restclient.models.*; +import org.vcell.restclient.api.AdminResourceApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("https://vcellapi-test.cam.uchc.edu"); + + + AdminResourceApi apiInstance = new AdminResourceApi(defaultClient); + try { + ApiResponse response = apiInstance.getUsageWithHttpInfo(); + System.out.println("Status code: " + response.getStatusCode()); + System.out.println("Response headers: " + response.getHeaders()); + System.out.println("Response body: " + response.getData()); + } catch (ApiException e) { + System.err.println("Exception when calling AdminResourceApi#getUsage"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Response headers: " + e.getResponseHeaders()); + System.err.println("Reason: " + e.getResponseBody()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +ApiResponse<[**File**](File.md)> + + +### Authorization + +[openId](../README.md#openId) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/pdf + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | The PDF report | - | +| **401** | Not Authorized | - | +| **403** | Not Allowed | - | + diff --git a/vcell-restclient/docs/UsersResourceApi.md b/vcell-restclient/docs/UsersResourceApi.md index c19fc47a2c..2a97e2fea3 100644 --- a/vcell-restclient/docs/UsersResourceApi.md +++ b/vcell-restclient/docs/UsersResourceApi.md @@ -591,7 +591,7 @@ public class Example { ### HTTP request headers - **Content-Type**: application/json -- **Accept**: application/json +- **Accept**: text/plain ### HTTP response details | Status code | Description | Response headers | @@ -661,7 +661,7 @@ ApiResponse<**Boolean**> ### HTTP request headers - **Content-Type**: application/json -- **Accept**: application/json +- **Accept**: text/plain ### HTTP response details | Status code | Description | Response headers | diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/api/AdminResourceApi.java b/vcell-restclient/src/main/java/org/vcell/restclient/api/AdminResourceApi.java new file mode 100644 index 0000000000..be5b616c31 --- /dev/null +++ b/vcell-restclient/src/main/java/org/vcell/restclient/api/AdminResourceApi.java @@ -0,0 +1,150 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package org.vcell.restclient.api; + +import org.vcell.restclient.ApiClient; +import org.vcell.restclient.ApiException; +import org.vcell.restclient.ApiResponse; +import org.vcell.restclient.Pair; + +import java.io.*; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.File; +import java.net.http.HttpRequest; +import java.nio.channels.Channels; +import java.nio.channels.Pipe; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +import java.util.ArrayList; +import java.util.StringJoiner; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class AdminResourceApi { + private final HttpClient memberVarHttpClient; + private final ObjectMapper memberVarObjectMapper; + private final String memberVarBaseUri; + private final Consumer memberVarInterceptor; + private final Duration memberVarReadTimeout; + private final Consumer> memberVarResponseInterceptor; + private final Consumer> memberVarAsyncResponseInterceptor; + + public AdminResourceApi() { + this(new ApiClient()); + } + + public AdminResourceApi(ApiClient apiClient) { + memberVarHttpClient = apiClient.getHttpClient(); + memberVarObjectMapper = apiClient.getObjectMapper(); + memberVarBaseUri = apiClient.getBaseUri(); + memberVarInterceptor = apiClient.getRequestInterceptor(); + memberVarReadTimeout = apiClient.getReadTimeout(); + memberVarResponseInterceptor = apiClient.getResponseInterceptor(); + memberVarAsyncResponseInterceptor = apiClient.getAsyncResponseInterceptor(); + } + + protected ApiException getApiException(String operationId, HttpResponse response) throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } + + private String formatExceptionMessage(String operationId, int statusCode, String body) { + if (body == null || body.isEmpty()) { + body = "[no body]"; + } + return operationId + " call failed with: " + statusCode + " - " + body; + } + + /** + * Get usage summary + * + * @return File + * @throws ApiException if fails to make API call + */ + public File getUsage() throws ApiException { + ApiResponse localVarResponse = getUsageWithHttpInfo(); + return localVarResponse.getData(); + } + + /** + * Get usage summary + * + * @return ApiResponse<File> + * @throws ApiException if fails to make API call + */ + public ApiResponse getUsageWithHttpInfo() throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getUsageRequestBuilder(); + try { + HttpResponse localVarResponse = memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("getUsage", localVarResponse); + } + // Create a temporary file to store the PDF + File tempFile = File.createTempFile("usage_summary", ".pdf"); + try (OutputStream out = new FileOutputStream(tempFile)) { + // Write the response body to the file + localVarResponse.body().transferTo(out); + } + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + tempFile + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getUsageRequestBuilder() throws ApiException { + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/api/v1/admin/usage"; + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/pdf"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } +} diff --git a/vcell-restclient/src/main/java/org/vcell/restclient/api/UsersResourceApi.java b/vcell-restclient/src/main/java/org/vcell/restclient/api/UsersResourceApi.java index 7c9cda02f3..70c7ef87a9 100644 --- a/vcell-restclient/src/main/java/org/vcell/restclient/api/UsersResourceApi.java +++ b/vcell-restclient/src/main/java/org/vcell/restclient/api/UsersResourceApi.java @@ -413,7 +413,7 @@ private HttpRequest.Builder mapUserRequestBuilder(UserLoginInfoForMapping userLo localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); localVarRequestBuilder.header("Content-Type", "application/json"); - localVarRequestBuilder.header("Accept", "application/json"); + localVarRequestBuilder.header("Accept", "text/plain"); try { byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes(userLoginInfoForMapping); diff --git a/vcell-restclient/src/test/java/org/vcell/restclient/api/AdminResourceApiTest.java b/vcell-restclient/src/test/java/org/vcell/restclient/api/AdminResourceApiTest.java new file mode 100644 index 0000000000..f1bcb77cc1 --- /dev/null +++ b/vcell-restclient/src/test/java/org/vcell/restclient/api/AdminResourceApiTest.java @@ -0,0 +1,53 @@ +/* + * VCell API + * VCell API + * + * The version of the OpenAPI document: 1.0.1 + * Contact: vcell_support@uchc.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.vcell.restclient.api; + +import org.vcell.restclient.ApiException; +import java.io.File; +import org.junit.Test; +import org.junit.Ignore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * API tests for AdminResourceApi + */ +@Ignore +public class AdminResourceApiTest { + + private final AdminResourceApi api = new AdminResourceApi(); + + + /** + * Get usage summary + * + * + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void getUsageTest() throws ApiException { + File response = + api.getUsage(); + + // TODO: test validations + } + +} diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/AdminDBTopLevel.java b/vcell-server/src/main/java/cbit/vcell/modeldb/AdminDBTopLevel.java index c00f9234ab..fb922d5966 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/AdminDBTopLevel.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/AdminDBTopLevel.java @@ -20,6 +20,7 @@ import org.vcell.db.DatabaseSyntax; import org.vcell.util.DataAccessException; import org.vcell.util.ObjectNotFoundException; +import org.vcell.util.PermissionException; import org.vcell.util.UseridIDExistsException; import org.vcell.util.document.ExternalDataIdentifier; import org.vcell.util.document.KeyValue; @@ -252,6 +253,145 @@ private static int htmlWithBreak(String descr, StringBuffer sb, Statement stmt, return val; } + private static int executeCountQuery(Statement stmt, String query) throws SQLException{ + lg.info(query); + ResultSet rset = stmt.executeQuery(query); + int val = 0; + if(rset.next()){ + val = rset.getInt(QUERY_VALUE); + } + rset.close(); + return val; + } + + + public record DbUsageSummary( + DbUserSimCount[] simCounts_7Days, + DbUserSimCount[] simCounts_30Days, + DbUserSimCount[] simCounts_90Days, + DbUserSimCount[] simCounts_180Days, + DbUserSimCount[] simCounts_365Days, + DbUsersRegisteredStats usersRegisteredStats, + int totalUsers, + int usersWithSims, + int biomodelCount, + int mathmodelCount, + int publicBiomodelCount, + int publicMathmodelCount, + int simCount, + int publicBiomodelSimCount, + int publicMathmodelSimCount) {} + public record DbUserSimCount(String username, int simCount) {} + public record DbUsersRegisteredStats(int last1Week, int last1Month, int last3Months, int last6Months, int last12Months) {} + + public synchronized DbUsageSummary getUsageSummary(User.SpecialUser user) throws SQLException, DataAccessException{ + if (!user.isAdmin()){ + throw new PermissionException("not authorized"); + } + Object lock = new Object(); + Connection con = conFactory.getConnection(lock); + Statement stmt = null; + try { + stmt = con.createStatement(); + int regUsers_7days = executeCountQuery(stmt, "select count(*) " + QUERY_VALUE + " from vc_userinfo where insertdate >= (current_date - 7)"); + int regUsers_30days = executeCountQuery(stmt, "select count(*) " + QUERY_VALUE + " from vc_userinfo where insertdate >= (current_date - 30)"); + int regUsers_90days = executeCountQuery(stmt, "select count(*) " + QUERY_VALUE + " from vc_userinfo where insertdate >= (current_date - 90)"); + int regUsers_180days = executeCountQuery(stmt, "select count(*) " + QUERY_VALUE + " from vc_userinfo where insertdate >= (current_date - 180)"); + int regUsers_365days = executeCountQuery(stmt, "select count(*) " + QUERY_VALUE + " from vc_userinfo where insertdate >= (current_date - 365)"); + + DbUsersRegisteredStats usersRegisteredStats = new DbUsersRegisteredStats( + regUsers_7days, regUsers_30days, regUsers_90days, regUsers_180days, regUsers_365days); + + String totalUsersQuery = "select count(*) " + QUERY_VALUE + " from vc_userinfo"; + int totalUsers = executeCountQuery(stmt, totalUsersQuery); + + String usersWithSimsQuery = "select count(distinct ownerref) " + QUERY_VALUE + " from vc_simulation"; + int usersWithSims = executeCountQuery(stmt, usersWithSimsQuery); + + String bmQuery = "select count(*) " + QUERY_VALUE + " from vc_biomodel"; + int biomodelCount = executeCountQuery(stmt, bmQuery); + + String mmQuery = "select count(*) " + QUERY_VALUE + " from vc_mathmodel"; + int mathmodelCount = executeCountQuery(stmt, mmQuery); + + String pubbmQuery = "select count(*) " + QUERY_VALUE + " from vc_biomodel where PRIVACY=0"; + int publicBiomodelCount = executeCountQuery(stmt, pubbmQuery); + + String pubmmQuery = "select count(*) " + QUERY_VALUE + " from vc_mathmodel where PRIVACY=0"; + int publicMathmodelCount = executeCountQuery(stmt, pubmmQuery); + + String allsims = "select count(*) " + QUERY_VALUE + " from vc_simulation"; + int simCount = executeCountQuery(stmt, allsims); + + String pubbmsimsQuery = "SELECT COUNT (*) " + QUERY_VALUE + " FROM VC_SIMULATION WHERE VC_SIMULATION.ID IN (" + + "SELECT DISTINCT VC_SIMULATION.ID FROM VC_BIOMODEL, VC_SIMULATION, VC_BIOMODELSIM " + + "WHERE VC_SIMULATION.ID = VC_BIOMODELSIM.simref " + + "AND VC_BIOMODEL.ID = VC_BIOMODELSIM.biomodelref " + + "AND vc_biomodel.privacy = 0)"; + int publicBiomodelSimCount = executeCountQuery(stmt, pubbmsimsQuery); + + String pubmmsimsQuery = "SELECT COUNT (*) " + QUERY_VALUE + " FROM VC_SIMULATION WHERE VC_SIMULATION.ID IN (" + + "SELECT DISTINCT VC_SIMULATION.ID FROM VC_MATHMODEL, VC_SIMULATION, VC_MATHMODELSIM " + + "WHERE VC_SIMULATION.ID = VC_MATHMODELSIM.simref " + + "AND VC_MATHMODEL.ID = VC_MATHMODELSIM.mathmodelref " + + "AND VC_MATHMODEL.privacy = 0)"; + int publicMathmodelSimCount = executeCountQuery(stmt, pubmmsimsQuery); + + DbUserSimCount[] userSimCounts_7Days = getUserSimCounts(stmt, 7); + DbUserSimCount[] userSimCounts_30Days = getUserSimCounts(stmt, 30); + DbUserSimCount[] userSimCounts_90Days = getUserSimCounts(stmt, 90); + DbUserSimCount[] userSimCounts_180Days = getUserSimCounts(stmt, 180); + DbUserSimCount[] userSimCounts_365Days = getUserSimCounts(stmt, 365); + + return new DbUsageSummary( + userSimCounts_7Days, + userSimCounts_30Days, + userSimCounts_90Days, + userSimCounts_180Days, + userSimCounts_365Days, + usersRegisteredStats, + totalUsers, + usersWithSims, + biomodelCount, + mathmodelCount, + publicBiomodelCount, + publicMathmodelCount, + simCount, + publicBiomodelSimCount, + publicMathmodelSimCount); + } catch(Throwable e){ + lg.error("failure in getSimulationJobStatusArray()", e); + handle_DataAccessException_SQLException(e); + return null; // never gets here; + } finally { + try { + if(stmt != null){ + stmt.close(); + } + } catch(Exception e){ + lg.error(e.getMessage(), e); + } + conFactory.release(con, lock); + } + } + + private DbUserSimCount[] getUserSimCounts(Statement stmt, int pastTime) throws SQLException { + ResultSet rset = stmt.executeQuery( + "SELECT userid, COUNT(vc_simulationjob.id) simcount FROM vc_userinfo, vc_simulation, vc_simulationjob" + + " WHERE vc_userinfo.id = vc_simulation.ownerref AND " + + "vc_simulationjob.simref = vc_simulation.id AND " + + "vc_simulationjob.submitdate >= (CURRENT_DATE -" + pastTime + ")" + " GROUP BY userid ORDER BY simcount desc"); + ArrayList userSimCounts = new ArrayList<>(); + while (rset.next()) { + String username = rset.getString(1); + int userSimCount = rset.getInt(2); + userSimCounts.add(new DbUserSimCount(username, userSimCount)); + } + rset.close(); + return userSimCounts.toArray(new DbUserSimCount[0]); + } + + public synchronized String getBasicStatistics() throws SQLException, DataAccessException{ Object lock = new Object(); Connection con = conFactory.getConnection(lock); @@ -260,19 +400,6 @@ public synchronized String getBasicStatistics() throws SQLException, DataAccessE StringBuffer sb = new StringBuffer( "" + "\n" + - "\n" + "\n" + ""); stmt = con.createStatement(); diff --git a/vcell-server/src/main/java/cbit/vcell/modeldb/DbDriver.java b/vcell-server/src/main/java/cbit/vcell/modeldb/DbDriver.java index d2403be525..4ad653c098 100644 --- a/vcell-server/src/main/java/cbit/vcell/modeldb/DbDriver.java +++ b/vcell-server/src/main/java/cbit/vcell/modeldb/DbDriver.java @@ -591,7 +591,7 @@ public static VCDocumentInfo curate(CurateSpec curateSpec, Connection con, User updatedVersionFlag = VersionFlag.Archived; } else if(curateSpec.getCurateType() == CurateSpec.PUBLISH){ //Must have PUBLISH rights - if(!dbVersion.getOwner().isPublisher()){ + if (!(user instanceof User.SpecialUser specialUser) || !specialUser.isPublisher()) { throw new PermissionException("Cannot curate " + vType.getTypeName() + " \"" + dbVersion.getName() + "\" (" + vKey + "), user " + user.getName() + " not granted PUBLISHING rights"); } //Must be ARCHIVED and Public before PUBLISH is allowed diff --git a/webapp-ng/src/app/app-routing.module.ts b/webapp-ng/src/app/app-routing.module.ts index fc8a1b5f14..f16be21302 100644 --- a/webapp-ng/src/app/app-routing.module.ts +++ b/webapp-ng/src/app/app-routing.module.ts @@ -6,8 +6,14 @@ import { ErrorComponent } from './pages/error/error.component'; import { AuthGuard } from '@auth0/auth0-angular'; import { PublicationListComponent } from './components/publication-list/publication-list.component'; import {LoginSuccessComponent} from "./pages/login-success/login-success.component"; +import {AdminComponent} from "./pages/admin/admin.component"; const routes: Routes = [ + { + path: 'admin', + component: AdminComponent, + canActivate: [AuthGuard], + }, { path: 'profile', component: ProfileComponent, diff --git a/webapp-ng/src/app/components/nav-bar/nav-bar.component.html b/webapp-ng/src/app/components/nav-bar/nav-bar.component.html index 0361f105f1..9e120fadda 100644 --- a/webapp-ng/src/app/components/nav-bar/nav-bar.component.html +++ b/webapp-ng/src/app/components/nav-bar/nav-bar.component.html @@ -27,6 +27,9 @@ +