├── tests ├── __init__.py ├── unit │ ├── __init__.py │ └── test_resource.py ├── functional │ ├── __init__.py │ ├── test_web.py │ ├── test_jazz_gc.py │ ├── oslc.py │ └── test_rm.py └── conftest.py ├── app ├── api │ ├── __init__.py │ ├── oauth │ │ ├── __init__.py │ │ ├── oslc_oauth.py │ │ └── pyoslc_app.py │ └── adapter │ │ ├── dialogs │ │ ├── __init__.py │ │ ├── templates │ │ │ └── dialogs │ │ │ │ ├── base.html │ │ │ │ ├── smallpreview.html │ │ │ │ ├── largepreview.html │ │ │ │ ├── creator.html │ │ │ │ └── selector.html │ │ ├── static │ │ │ ├── css │ │ │ │ ├── adaptor.css.lost │ │ │ │ └── adaptor.css │ │ │ ├── js │ │ │ │ └── preview.js │ │ │ └── delegatedUI.js │ │ └── routes.py │ │ ├── mappings │ │ ├── __init__.py │ │ └── specification.py │ │ ├── namespaces │ │ ├── __init__.py │ │ ├── config │ │ │ ├── __init__.py │ │ │ └── routes.py │ │ ├── rm │ │ │ ├── __init__.py │ │ │ ├── parsers.py │ │ │ ├── models.py │ │ │ └── csv_requirement_repository.py │ │ └── business.py │ │ ├── resources │ │ ├── __init__.py │ │ ├── repository.py │ │ └── resource_service.py │ │ ├── services │ │ ├── __init__.py │ │ ├── factories.py │ │ ├── specification.py │ │ └── providers.py │ │ ├── static │ │ ├── pyicon16.ico │ │ ├── pyicon24.ico │ │ ├── pyicon32.ico │ │ ├── pyicon72.ico │ │ └── delegatedUI.js │ │ ├── vocabulary.py │ │ ├── exceptions.py │ │ ├── oslc.py │ │ ├── manager.py │ │ ├── forms.py │ │ └── __init__.py ├── web │ ├── __init__.py │ ├── routes.py │ └── templates │ │ ├── web │ │ ├── index.html │ │ ├── requirement_list.html │ │ └── requirement.html │ │ └── base.html ├── wsgi.py ├── config.py └── __init__.py ├── pyoslc ├── rest │ ├── __init__.py │ └── resource.py ├── resources │ ├── __init__.py │ ├── domains │ │ ├── __init__.py │ │ └── qm.py │ ├── jazz.py │ └── factories.py ├── serializers │ ├── __init__.py │ ├── configxml.py │ └── jazzxml.py ├── vocabularies │ ├── __init__.py │ ├── data.py │ ├── am.py │ ├── config.py │ ├── trs.py │ ├── jfs.py │ ├── rm.py │ ├── cm.py │ ├── jazz.py │ └── core.py ├── __init__.py └── helpers.py ├── pyoslc_oauth ├── client │ └── __init__.py ├── routes │ ├── __init__.py │ ├── oauth.py │ └── consumer.py ├── templates │ └── pyoslc_oauth │ │ ├── selection.html │ │ ├── admin_login.html │ │ ├── base.html │ │ ├── publisher.html │ │ ├── configuration.html │ │ ├── macros.html │ │ ├── components.html │ │ ├── authorize.html │ │ ├── stream.html │ │ ├── scr.html │ │ ├── selection.xhtml │ │ └── manage_consumer.html ├── database.py ├── login_manager.py ├── vocabulary.py ├── server.py ├── __init__.py ├── forms.py └── models.py ├── .env ├── .flaskenv ├── docs ├── source │ ├── _static │ │ ├── 01.png │ │ └── 02.png │ ├── index.rst │ ├── libraries.rst │ ├── getting_the_code.rst │ └── conf.py ├── Makefile └── make.bat ├── documents └── PyOSLC Architecture.pdf ├── MANIFEST.in ├── setup.cfg ├── .gitignore ├── initialize.py ├── requirements.txt ├── tox.ini ├── .github └── workflows │ ├── publish.yml │ └── tests.yml ├── examples └── specifications.csv ├── LICENSE ├── setup.py └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc/rest/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/oauth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc/resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc_oauth/client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc_oauth/routes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | AUTHLIB_INSECURE_TRANSPORT=True -------------------------------------------------------------------------------- /app/api/adapter/dialogs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/adapter/mappings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/adapter/namespaces/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/adapter/resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/adapter/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc/resources/domains/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/adapter/namespaces/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/selection.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.flaskenv: -------------------------------------------------------------------------------- 1 | FLASK_APP=app.wsgi:app 2 | FLASK_ENV=development 3 | FLASK_DEBUG=True 4 | -------------------------------------------------------------------------------- /docs/source/_static/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cslab/pyoslc/HEAD/docs/source/_static/01.png -------------------------------------------------------------------------------- /docs/source/_static/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cslab/pyoslc/HEAD/docs/source/_static/02.png -------------------------------------------------------------------------------- /app/wsgi.py: -------------------------------------------------------------------------------- 1 | from app import create_app 2 | from app.config import Config 3 | 4 | app = create_app(Config) 5 | -------------------------------------------------------------------------------- /app/api/adapter/static/pyicon16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cslab/pyoslc/HEAD/app/api/adapter/static/pyicon16.ico -------------------------------------------------------------------------------- /app/api/adapter/static/pyicon24.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cslab/pyoslc/HEAD/app/api/adapter/static/pyicon24.ico -------------------------------------------------------------------------------- /app/api/adapter/static/pyicon32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cslab/pyoslc/HEAD/app/api/adapter/static/pyicon32.ico -------------------------------------------------------------------------------- /app/api/adapter/static/pyicon72.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cslab/pyoslc/HEAD/app/api/adapter/static/pyicon72.ico -------------------------------------------------------------------------------- /documents/PyOSLC Architecture.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cslab/pyoslc/HEAD/documents/PyOSLC Architecture.pdf -------------------------------------------------------------------------------- /pyoslc/resources/domains/qm.py: -------------------------------------------------------------------------------- 1 | from pyoslc.resources.models import BaseResource 2 | 3 | 4 | class TestCase(BaseResource): 5 | pass 6 | -------------------------------------------------------------------------------- /pyoslc_oauth/database.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() 4 | 5 | 6 | def init_app(app): 7 | db.init_app(app) 8 | -------------------------------------------------------------------------------- /pyoslc/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python implementation for the OSLC specificaction 3 | """ 4 | 5 | __version__ = '1.0.0-dev' 6 | __date__ = '2018/12/01' 7 | -------------------------------------------------------------------------------- /pyoslc_oauth/login_manager.py: -------------------------------------------------------------------------------- 1 | from flask_login import LoginManager 2 | 3 | login = LoginManager() 4 | 5 | 6 | def init_app(app): 7 | login.init_app(app) 8 | -------------------------------------------------------------------------------- /app/api/adapter/resources/repository.py: -------------------------------------------------------------------------------- 1 | class Repository(object): 2 | 3 | def __init__(self, title): 4 | self.title = title 5 | 6 | def get(self): 7 | pass 8 | -------------------------------------------------------------------------------- /tests/unit/test_resource.py: -------------------------------------------------------------------------------- 1 | from pyoslc.resources.models import ServiceProvider 2 | 3 | 4 | def test_service_provider(): 5 | sp = ServiceProvider() 6 | 7 | assert sp is not None 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | global-exclude *.pyc 2 | 3 | graft pyoslc/app/api/adapter/static 4 | graft pyoslc/app/api/adapter/dialogs/static 5 | graft pyoslc/app/api/adapter/dialogs/templates 6 | graft pyoslc/app/web/templates -------------------------------------------------------------------------------- /app/web/routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, render_template 2 | 3 | bp = Blueprint('web', __name__, template_folder='templates') 4 | 5 | 6 | @bp.route('/') 7 | def index(): 8 | return render_template("web/index.html") 9 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/admin_login.html: -------------------------------------------------------------------------------- 1 | {% extends "routes/base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block content %} 5 | {{ wtf.quick_form(form, action=form.action) }} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /app/api/oauth/oslc_oauth.py: -------------------------------------------------------------------------------- 1 | import pyoslc_oauth 2 | from app.api.oauth.pyoslc_app import PyOSLCApplication 3 | 4 | pyoslc = PyOSLCApplication('PyOSLC Contact Software') 5 | 6 | 7 | def init_app(app): 8 | pyoslc_oauth.init_app(app, pyoslc) 9 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/base.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | 3 | {% block title %}PyOSLC OAuth{% endblock %} 4 | 5 | {% block navbar %} 6 | {% endblock %} 7 | 8 | {% block content %} 9 | {% block form %}{% endblock %} 10 | {% endblock %} -------------------------------------------------------------------------------- /app/api/adapter/dialogs/templates/dialogs/base.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | 3 | {% block title %}PyOSLC OAuth{% endblock %} 4 | 5 | {% block navbar %} 6 | {% endblock %} 7 | 8 | {% block content %} 9 | {% block form %}{% endblock %} 10 | {% endblock %} -------------------------------------------------------------------------------- /app/api/adapter/vocabulary.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | 5 | PYOSLC = ClosedNamespace( 6 | uri=URIRef("http://example.com/ns/pyoslc#"), 7 | terms=[ 8 | "TrackedResourceSetProvider", 9 | ] 10 | ) 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = LICENSE 3 | 4 | [tool:pytest] 5 | minversion = 2.7 6 | testpaths = tests 7 | 8 | [coverage:run] 9 | branch = True 10 | source = 11 | pyoslc 12 | pyoslc_oauth 13 | tests 14 | 15 | [coverage:paths] 16 | source = 17 | pyoslc 18 | pyoslc_oauth 19 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/data.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | OSLCData = ClosedNamespace( 5 | uri=URIRef("http://open-services.net/ns/servicemanagement/1.0/"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | # RDF Properties in this namespace 9 | ] 10 | ) 11 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/am.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | OSLC_AM = ClosedNamespace( 5 | uri=URIRef("http://open-services.net/ns/am#"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | "LinkType", 9 | 10 | # RDF Properties in this namespace 11 | "amServiceProviders", 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | .idea/ 4 | 5 | venv/ 6 | 7 | *.pyc 8 | __pycache__/ 9 | 10 | .pytest_cache/ 11 | .coverage 12 | htmlcov/ 13 | 14 | dist/ 15 | build/ 16 | *.egg-info/ 17 | 18 | logs/ 19 | webservice/logs/ 20 | 21 | _cache 22 | app/oauth.sqlite 23 | pyoslc_oauth/OAuthStore.rdf 24 | 25 | docs/build 26 | 27 | .tox/ 28 | test-reports/ 29 | 30 | .python-version 31 | -------------------------------------------------------------------------------- /initialize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import subprocess 4 | import sys 5 | 6 | subprocess.call(['virtualenv', 'venv']) 7 | if sys.platform == 'win32': 8 | bin = 'Scripts' 9 | else: 10 | bin = 'bin' 11 | 12 | requirements = open("requirements.txt", "r") 13 | 14 | for line in requirements: 15 | subprocess.call([os.path.join('venv', bin, 'pip'), 'install', '--upgrade', line]) 16 | -------------------------------------------------------------------------------- /app/api/adapter/exceptions.py: -------------------------------------------------------------------------------- 1 | from werkzeug.exceptions import HTTPException 2 | 3 | 4 | class NotModified(HTTPException): 5 | """*304* `Not Modified` 6 | 7 | Raise if the client sent a resource with no changes to be stored. 8 | """ 9 | 10 | code = 304 11 | description = ( 12 | "The client sent the same resource without changes to the server, " 13 | "it can not be updated." 14 | ) 15 | -------------------------------------------------------------------------------- /app/web/templates/web/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block button_home %} 4 | Home 5 | {% endblock %} 6 | 7 | {% block header %} 8 |

{% block title %}{% endblock %}

9 | {% endblock %} 10 | 11 | {% block content %} 12 | List Requirements 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /pyoslc_oauth/vocabulary.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | OAUTH = ClosedNamespace( 5 | uri=URIRef("http://example.com/pyoslc/server/oauth#"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | "Consumer", 9 | 10 | # RDF Properties in this namespace 11 | "consumerName", "consumerKey", 12 | "consumerSecret", "provisional", 13 | "trusted", "callback" 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /pyoslc/helpers.py: -------------------------------------------------------------------------------- 1 | def build_uri(base, *parts): 2 | """ 3 | This method helps to generate a URI based on a list of words 4 | passed as a parameter 5 | :param base: The URI base e.g.: http://examples.com 6 | :param parts: List of elementos for generatig the URI e.g.: [oooooslc,service,catalog] 7 | :return: The URI formed with the base and the parts e.g.: http://examples.com/oooooslc/service/catalog 8 | """ 9 | if parts: 10 | base = base + '/'.join(parts) 11 | return base 12 | -------------------------------------------------------------------------------- /app/api/adapter/namespaces/rm/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_restx import Namespace 2 | 3 | from app.api.adapter.namespaces.rm.routes import RequirementList, RequirementItem 4 | 5 | rm_ns = Namespace(name='rm', description='Requirements Management', path='/rm') 6 | 7 | rm_ns.add_resource(RequirementList, "/requirement") 8 | rm_ns.add_resource(RequirementItem, "/requirement/") 9 | # rm_ns.add_resource(UploadCollection, "/collection") 10 | # rm_ns.add_resource(QueryCapability, "/query_capability", defaults={'query_capability_id': ''}) 11 | -------------------------------------------------------------------------------- /app/api/adapter/oslc.py: -------------------------------------------------------------------------------- 1 | from app.api.adapter import bp, api 2 | from app.api.adapter.dialogs.routes import dialog_bp 3 | from app.api.adapter.namespaces.config.routes import config_ns 4 | from app.api.adapter.namespaces.core import adapter_ns 5 | from app.api.adapter.namespaces.rm import rm_ns 6 | 7 | 8 | def init_app(app): 9 | app.register_blueprint(bp, url_prefix='/oslc') 10 | api.add_namespace(adapter_ns) 11 | api.add_namespace(rm_ns) 12 | api.add_namespace(config_ns) 13 | app.register_blueprint(dialog_bp, url_prefix='/oslc/services') 14 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/config.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | OSLC_CONFIG = ClosedNamespace( 5 | uri=URIRef("http://open-services.net/ns/config#"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | "Configuration", 9 | "Stream", 10 | 11 | # RDF Properties in this namespace 12 | "cmServiceProviders", 13 | ] 14 | ) 15 | 16 | PROV = ClosedNamespace( 17 | uri=URIRef("http://www.w3.org/ns/prov#"), 18 | terms=[ 19 | "wasDerivedFrom", "wasRevisionOf", "wasGeneratedBy" 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /app/web/templates/web/requirement_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block button_home %} 4 | Home 5 | {% endblock %} 6 | 7 | {% block header %} 8 |

{% block title %}Listing the Requirements.{% endblock %}

9 | {% endblock %} 10 | 11 | {% block content %} 12 | 13 | 20 | 21 | {% endblock %} 22 | 23 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/trs.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | OSLC_TRS = ClosedNamespace( 5 | uri=URIRef("http://open-services.net/ns/core/trs#"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | "ResourceSet", "Resource", 9 | "TrackedResourceSet", "ChangeLog", 10 | "Creation", "Modification", "Deletion", 11 | 12 | # RDF Properties in this namespace 13 | "trackedResourceSet", 14 | 15 | "base", "changeLog", "cutoffEvent", 16 | "change", "previous", "changed", "order", 17 | 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /pyoslc/vocabularies/jfs.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | JFS = ClosedNamespace( 5 | uri=URIRef("http://jazz.net/xmlns/prod/jazz/jfs/1.0/"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | 9 | # RDF Properties in this namespace 10 | # for OAuth 11 | "oauthRealmName", "oauthDomain", 12 | "oauthRequestConsumerKeyUrl", 13 | "oauthApprovalModuleUrl", 14 | "oauthRequestTokenUrl", 15 | "oauthUserAuthorizationUrl", 16 | "oauthAccessTokenUrl", 17 | "nonLocalizedTitle", 18 | "version", 19 | "instanceName", 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /app/api/adapter/manager.py: -------------------------------------------------------------------------------- 1 | from app.api.adapter.services.specification import Specification 2 | 3 | 4 | class CSVImplementation(object): 5 | 6 | @classmethod 7 | def get_service_provider_info(cls): 8 | service_providers = [{ 9 | 'id': 'Project-1', 10 | 'name': 'PyOSLC Service Provider for Project 1', 11 | 'class': Specification 12 | }] 13 | 14 | return service_providers 15 | 16 | @classmethod 17 | def get_configuration_info(cls): 18 | components = [{ 19 | 'id': 'Component-1', 20 | 'name': 'PyOSLC Configuration Component' 21 | }] 22 | 23 | return components 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-dotenv==0.18.0 ; python_version <= '2.7' 2 | python-dotenv > 0.18.0 ; python_version > '2.7' 3 | 4 | Flask == 1.0.2 ; python_version <= '2.7' 5 | Flask > 1.0.2 ; python_version > '2.7' 6 | 7 | Flask-RESTx == 0.5.1 ; python_version <= '2.7' 8 | Flask-RESTx > 0.5.1 ; python_version > '2.7' 9 | 10 | Flask-CORS 11 | Flask-WTF 12 | Flask-Bootstrap 13 | Flask-SQLAlchemy 14 | Flask-Login 15 | cachelib~=0.1.1 16 | 17 | RDFLib==5.0.0 ; python_version <= '2.7' 18 | RDFLib-jsonld==0.6.1 ; python_version <= '2.7' 19 | RDFLib>=6.0.0 ; python_version > '2.7' 20 | 21 | Authlib~=0.14.3 22 | 23 | Werkzeug == 1.0.1 ; python_version <= '2.7' 24 | Werkzeug > 1.0.1 ; python_version > '2.7' 25 | requests 26 | pytest 27 | 28 | #Sphinx==1.8.5 29 | -------------------------------------------------------------------------------- /pyoslc_oauth/server.py: -------------------------------------------------------------------------------- 1 | from authlib.integrations.flask_oauth1 import AuthorizationServer, register_nonce_hooks, \ 2 | register_temporary_credential_hooks 3 | from authlib.integrations.sqla_oauth1 import create_query_client_func, register_token_credential_hooks 4 | 5 | from pyoslc_oauth.database import db 6 | from pyoslc_oauth.models import Client, cache, TokenCredential 7 | 8 | query_client = create_query_client_func(db.session, Client) 9 | auth_server = AuthorizationServer(query_client=query_client) 10 | 11 | 12 | def init_app(app): 13 | auth_server.init_app(app) 14 | register_nonce_hooks(auth_server, cache) 15 | register_temporary_credential_hooks(auth_server, cache) 16 | register_token_credential_hooks(auth_server, db.session, TokenCredential) 17 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/rm.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | OSLC_RM = ClosedNamespace( 5 | uri=URIRef("http://open-services.net/ns/rm#"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | "Requirement", "RequirementCollection", 9 | 10 | # RDF Properties in this namespace 11 | # for Requirement 12 | "affectedBy", "elaboratedBy", "implementedBy", 13 | "specifiedBy", "satisfiedBy", "trackedBy", 14 | "validatedBy", 15 | 16 | # for RequirementCollection 17 | "uses", 18 | # General 19 | "elaborates", "specifies", "satisfies", 20 | "decomposedBy", "decomposes", 21 | "constrainedBy", "constrains", 22 | "rmServiceProviders", 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # ignore = F401 pyoslc/resources/domains/rm.py 3 | per-file-ignores = 4 | # imported but unused 5 | pyoslc/resources/domains/rm.py: F401 6 | max-line-length = 120 7 | exclude = docs/*, tests/* 8 | 9 | [pytest] 10 | filterwarnings = 11 | ignore::pytest.PytestUnknownMarkWarning 12 | ignore::DeprecationWarning 13 | 14 | # log_cli = true 15 | # log_cli_level = debug 16 | 17 | # log_format = %(asctime)s %(levelname)s %(message)s 18 | # log_date_format = %Y-%m-%d %H:%M:%S 19 | 20 | [tox] 21 | envlist = 22 | py27 23 | py37 24 | py38 25 | py39 26 | 27 | [testenv] 28 | deps = 29 | flask_wtf 30 | flask_bootstrap 31 | flask_sqlalchemy 32 | flask_login 33 | flake8 34 | pytest 35 | 36 | commands = 37 | flake8 pyoslc 38 | pytest -v 39 | -------------------------------------------------------------------------------- /app/api/adapter/dialogs/templates/dialogs/smallpreview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ title }} 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
Specification ID:
19 |
{{ requirement.identifier }}
20 |
Description
21 |
{{ requirement.description }}
22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /app/api/adapter/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, SubmitField, SelectField 3 | from wtforms.validators import DataRequired 4 | 5 | 6 | class SpecificationForm(FlaskForm): 7 | specification_id = StringField(validators=[DataRequired()], render_kw={'class_': 'form-control-sm'}) 8 | title = StringField(validators=[DataRequired()]) 9 | description = StringField(validators=[DataRequired()]) 10 | author = StringField(validators=[DataRequired()]) 11 | product = StringField(validators=[DataRequired()]) 12 | subject = StringField(validators=[DataRequired()]) 13 | source = StringField(validators=[DataRequired()]) 14 | category = StringField(validators=[DataRequired()]) 15 | 16 | 17 | class SelectSpecificationForm(FlaskForm): 18 | title = StringField(validators=[DataRequired()]) 19 | selection = SelectField(option_widget="size=10") 20 | submit = SubmitField("Search") 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/publisher.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | PyOSLC-Adaptor Project 7 | PyOSLC Specification Management 8 | This application provides the capabilities to create and manage requirements 9 | http://rm.provider.local 10 | 6.0.6 11 | 12 | /pyoslc 13 | 14 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | 5 | base_dir = os.path.abspath(os.path.dirname(__file__)) 6 | load_dotenv(os.path.join(base_dir, '.env')) 7 | 8 | 9 | class Config(object): 10 | FLASK_ENV = os.environ.get('FLASK_ENV') 11 | SECRET_KEY = os.environ.get('SECRET_KEY') or 'this_value_should_be_updated' 12 | FLASK_DEBUG = True 13 | 14 | DATABASE_URL = os.environ.get('DATABASE_URL', None) 15 | 16 | SQLALCHEMY_DATABASE_URI = DATABASE_URL or 'sqlite:///' + os.path.join(base_dir, 'oauth.sqlite') 17 | SQLALCHEMY_ECHO = False 18 | SQLALCHEMY_TRACK_MODIFICATIONS = True 19 | 20 | OAUTH_CACHE_DIR = '_cache' 21 | 22 | MAIL_SERVER = None, 23 | LOG_TO_STDOUT = None, 24 | 25 | # AUTHLIB_INSECURE_TRANSPORT = True 26 | # OAUTHLIB_INSECURE_TRANSPORT = True 27 | 28 | # OAUTH1_PROVIDER_ENFORCE_SSL = False 29 | # OAUTH1_PROVIDER_KEY_LENGTH = (10, 100) 30 | 31 | # BASE_URI = 'http://examples.org/' 32 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/cm.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | OSLC_CM = ClosedNamespace( 5 | uri=URIRef("http://open-services.net/ns/cm#"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | "ChangeNotice", "ChangeRequest", "Defect", 9 | "Enhancement", "Priority", "ReviewTask", "Severity", 10 | "State", "Task" 11 | 12 | # RDF Properties in this namespace 13 | "affectedByDefect", "affectsPlanItem", 14 | "affectsRequirement", "affectsTestResult", 15 | "authorizer", "blocksTestExecutionRecord", 16 | "closeDate", "implementsRequirement", "parent", 17 | "priority", "relatedChangeRequest", "relatedTestCase", 18 | "relatedTestExecutionRecord", "relatedTestPlan", 19 | "relatedTestScript", "severity", "state", "status", 20 | "testedByTestCase", "tracksChangeSet", "tracksRequirement", 21 | "cmServiceProviders", 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/configuration.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | PyOSLC Configuration Management Service Provider 11 | 12 | 13 | 14 | Configuration Services Provided by the PyOSLC application. 15 | PyOSLC Configuration Management Catalog 16 | 17 | -------------------------------------------------------------------------------- /app/api/oauth/pyoslc_app.py: -------------------------------------------------------------------------------- 1 | from flask import request 2 | from flask_login import login_user 3 | 4 | from pyoslc_oauth import OAuthApplication 5 | from pyoslc_oauth.models import User 6 | from pyoslc_oauth.resources import OAuthException 7 | 8 | 9 | class PyOSLCApplication(OAuthApplication): 10 | """ 11 | This application was implemented for managing the 12 | authentication of the adapter, it extends the 13 | OAuthApplication from pyoslc to meet the implementation 14 | of the pyoslc authentication process. 15 | """ 16 | 17 | def get_realm(self): 18 | pass 19 | 20 | def is_authenticated(self): 21 | pass 22 | 23 | def login(self, username, password): 24 | user = User.query.filter_by(username=username).first() 25 | if not user or not user.check_password(password): 26 | raise OAuthException('Email or password is invalid.') 27 | 28 | login_user(user) 29 | 30 | def is_admin_session(self): 31 | return request.args.get('admin') 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload --repository testpypi dist/* 32 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. PyOSLC documentation master file, created by 2 | sphinx-quickstart on Mon Sep 14 11:40:50 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to PyOSLC's documentation! 7 | ================================== 8 | 9 | Introduction. 10 | ------------- 11 | The PyOSLC project was developed as set of classes and libraries packaged 12 | as a SDK which is aimed to build REST-based API’s that allows us 13 | to implement OSLC (Open Services for Lifecycle Collaboration) projects 14 | that meet the specifications for creating REST services to enable 15 | the interoperability of heterogeneous products and services. 16 | 17 | 18 | .. toctree:: 19 | :maxdepth: 3 20 | :caption: Contents: 21 | 22 | libraries 23 | getting_the_code 24 | getting_started 25 | creating_an_oslc_api 26 | 27 | 28 | 29 | Indices and tables 30 | ================== 31 | 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | -------------------------------------------------------------------------------- /pyoslc_oauth/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask_bootstrap import Bootstrap 4 | 5 | from pyoslc_oauth import database, server 6 | from pyoslc_oauth.login_manager import login 7 | from pyoslc_oauth.resources import OAuthConfiguration, FileSystemConsumerStore, OAuthApplication, OSLCOAuthConsumer 8 | from pyoslc_oauth.routes.consumer import consumer_bp 9 | from pyoslc_oauth.routes.oauth import oauth_bp 10 | 11 | base_dir = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | 14 | oauth_config = OAuthConfiguration() 15 | file_consumer = FileSystemConsumerStore(os.path.join(base_dir, 'OAuthStore.rdf')) 16 | oauth_app = OAuthApplication('PyOLSC') 17 | client = OSLCOAuthConsumer() 18 | 19 | 20 | def init_app(app, oslc_oauth_app=None): 21 | database.init_app(app) 22 | login.init_app(app) 23 | oauth_config.consumer_store = file_consumer 24 | oauth_config.application = oslc_oauth_app or oauth_app 25 | app.register_blueprint(oauth_bp) 26 | app.register_blueprint(consumer_bp) 27 | Bootstrap(app) 28 | server.init_app(app) 29 | -------------------------------------------------------------------------------- /examples/specifications.csv: -------------------------------------------------------------------------------- 1 | Specification_id;Product;Project;Title;Description;Source;Author;Category;Discipline;Revision;Target_Value;Degree_of_fulfillment;Status 2 | X1C2V3B1;SDK-Dev;Project-1;The ACRV shall provide medical life-support accommodations for one crew member;The OSLC RM Specification needs to be awesome 1;Ian Altman;Mario;Customer Requirement;Software Development;0;1;0;Draft 3 | X1C2V3B2;SDK-Dev;Project-1;The system shall provide six degrees of freedom control;The OSLC RM Specification needs to be awesome 2;Ian Altman;Mario;Customer Requirement;Software Development;0;1;0;Draft 4 | X1C2V3B3;SDK-Dev;Project-1;The SAFER FTA should not limit EVA crewmember mobility;The OSLC RM Specification needs to be awesome 3;Ian Altman;Mario;Customer Requirement;Software Development;0;1;0;Draft 5 | X1C2V3B4;SDK-Dev;Project-1;OSLC RM Spec 4;The OSLC RM Specification needs to be awesome 4;Ian Altman;Mario;Customer Requirement;Software Development;0;1;0;Draft 6 | X1C2V3B5;SDK-Dev;Project-1;OSLC RM Spec 5;The OSLC RM Specification needs to be awesome 5;Ian Altman;Mario;Customer Requirement;Software Development;0;1;0;Draft 7 | -------------------------------------------------------------------------------- /app/api/adapter/dialogs/static/css/adaptor.css.lost: -------------------------------------------------------------------------------- 1 | 2 | Thu Feb 15 14:10:44 EST 2018 3 | ================================================================================ 4 | Start of user code "Copyright Header" */ 5 | /******************************************************************************* 6 | Copyright (c) 2017 KTH Royal Institute of Technology. 7 | 8 | All rights reserved. This program and the accompanying materials 9 | are made available under the terms of the Eclipse Public License v1.0 10 | and Eclipse Distribution License v. 1.0 which accompanies this distribution. 11 | 12 | The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html 13 | and the Eclipse Distribution License is available at 14 | http://www.eclipse.org/org/documents/edl-v10.php. 15 | 16 | Contributors: 17 | 18 | Fr�d�ric Loiret - Switch the template to Bootstrap (519699) 19 | Andrii Berezovskyi - Support for UI Preview (494303) 20 | 21 | This file is generated by org.eclipse.lyo.oslc4j.codegenerator 22 | *******************************************************************************/ 23 | /* End of user code 24 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/macros.html: -------------------------------------------------------------------------------- 1 | {% macro render_field(field, label_html=None) %} 2 |
3 | {% if label_html %} 4 | 5 | {% else %} 6 | {{ field.label() }} 7 | {% endif %} 8 | {% if field.flags.required %} 9 | {{ field(class_="form-field_input", required=True) }} 10 | {% else %} 11 | {{ field(class_="form-field_input") }} 12 | {% endif %} 13 | {% if field.errors %} 14 | 19 | {% endif %} 20 |
21 | {% endmacro %} 22 | 23 | {% macro render_form(form) %} 24 | {% for field in form.hidden_fields() %} 25 | {{ field }} 26 | {% endfor %} 27 | {% for field in form.visible_fields() %} 28 | {{ render_field(field) }} 29 | {% endfor %} 30 | {% endmacro %} 31 | -------------------------------------------------------------------------------- /app/api/adapter/dialogs/templates/dialogs/largepreview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ title }} 9 | 10 | 11 | 12 |
13 |
14 |
15 |
Specification ID:
16 |
{{ requirement.identifier }}
17 |
Description
18 |
{{ requirement.description }}
19 |
Title
20 |
{{ requirement.title }}
21 |
Product
22 |
{{ requirement.short_title }}
23 |
Subject
24 |
{{ requirement.subject }}
25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from app import create_app 4 | from app.config import Config 5 | from tests.functional.oslc import PyOSLC 6 | 7 | 8 | @pytest.fixture(scope='session') 9 | def app(): 10 | """ 11 | Initializing the Flask application for the Minerva OSLC API 12 | by passing the Config class as the configuration 13 | 14 | :return: app: Flask application 15 | """ 16 | app = create_app('testing') 17 | app.testing = True 18 | yield app 19 | 20 | 21 | @pytest.fixture(scope='session') 22 | def client(): 23 | """ 24 | Getting the test_client instance for the Flask application 25 | for executing and validating the tests 26 | 27 | :param app: Flask application 28 | :return: client: Client with test configuration 29 | """ 30 | # Create a test client using the Flask application configured for testing 31 | app = create_app(Config) 32 | app.testing = True 33 | with app.test_client() as client: 34 | # Establish an application context 35 | with app.app_context(): 36 | yield client # this is where the testing happens! 37 | 38 | 39 | @pytest.fixture 40 | def pyoslc(client): 41 | return PyOSLC(client) 42 | -------------------------------------------------------------------------------- /app/web/templates/web/requirement.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block button_home %} 4 | List Requirements 5 | Home 6 | {% endblock %} 7 | 8 | {% block header %} 9 |

{% block title %}Describing the Requirement {{ id }}.{% endblock %}

10 | {% endblock %} 11 | 12 | {% block content %} 13 | 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for s, p, o in statements %} 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 | 33 |
PredicateObject
{{ p }}{{ o }}
34 |
35 |
36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/components.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | PyOSLC Configuration Picker 12 | PyOSLC Configuration Picker 13 | 14 | 600px 15 | 500px 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/jazz.py: -------------------------------------------------------------------------------- 1 | from rdflib import URIRef 2 | from rdflib.namespace import ClosedNamespace 3 | 4 | JAZZ_DISCOVERY = ClosedNamespace( 5 | uri=URIRef("http://jazz.net/xmlns/prod/jazz/discovery/1.0/"), 6 | terms=[ 7 | # RDFS Classes in this namespace 8 | "Friend", "RootServices", 9 | 10 | # RDF Properties in this namespace 11 | # for Friends 12 | "friends", "rootServices", 13 | ] 14 | ) 15 | 16 | JAZZ_PROCESS = ClosedNamespace( 17 | uri=URIRef("http://jazz.net/xmlns/prod/jazz/process/1.0/"), 18 | terms=[ 19 | "supportContributionsToLinkIndexProvider", 20 | "supportLinkDiscoveryViaLinkIndexProvider", 21 | "globalConfigurationAware", 22 | "supportOSLCSimpleQuery" 23 | ] 24 | ) 25 | 26 | OSLC_RM_JAZZ = ClosedNamespace( 27 | uri=URIRef("http://open-services.net/xmlns/rm/1.0/"), 28 | terms=[ 29 | "rmServiceProviders" 30 | ] 31 | ) 32 | 33 | OSLC_CM_JAZZ = ClosedNamespace( 34 | uri=URIRef("http://open-services.net/xmlns/cm/1.0/"), 35 | terms=[ 36 | ] 37 | ) 38 | 39 | JAZZ_CONFIG = ClosedNamespace( 40 | uri=URIRef("http://jazz.net/ns/vvc#"), 41 | terms=[ 42 | "Configuration" 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /app/api/adapter/resources/resource_service.py: -------------------------------------------------------------------------------- 1 | _service_resources = {} 2 | 3 | 4 | class ServiceResourceException(Exception): 5 | def __init__(self, msg=None): 6 | Exception.__init__(self, msg) 7 | self.msg = msg 8 | 9 | 10 | class ServiceResourceDescription(object): 11 | 12 | def __init__(self, name, kind, module_path, class_name): 13 | self.name = name 14 | self.kind = kind 15 | self.module_path = module_path 16 | self.class_name = class_name 17 | self._class = None 18 | 19 | def get_class(self): 20 | if self._class is None: 21 | module = __import__(self.module_path, globals(), locals(), [""]) 22 | self._class = getattr(module, self.class_name) 23 | return self._class 24 | 25 | 26 | def config_service_resource(name, kind, module_path, class_name): 27 | srd = ServiceResourceDescription(name, kind, module_path, class_name) 28 | _service_resources[(name, kind)] = srd 29 | 30 | 31 | def get_service_resources(kind): 32 | try: 33 | rs = [_service_resources[r] for r in _service_resources if r[1] == kind] 34 | except KeyError: 35 | raise ServiceResourceException( 36 | "No Service Resource registered for (%s)" % kind) 37 | return [r.get_class() for r in rs] 38 | -------------------------------------------------------------------------------- /docs/source/libraries.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Libraries used. 3 | =============== 4 | 5 | In the development of the **PyOSLC** project there were different libraries used 6 | for different goals or to cover specific requirements. 7 | 8 | All these libraries meet a specific objective in the project but there are 9 | some of them that need to be described. 10 | 11 | Flask and extensions. 12 | ===================== 13 | Since **PyOSLC** will be used to deploy web services, Flask was selected to create 14 | the web application and some other extensions were used to extend the web 15 | application as a REST-based API which is also able to manage templates, forms, 16 | logins, and databases for manipulating different type or requests and data. 17 | 18 | RDFLib and serializers. 19 | ======================= 20 | Another main library to mention is RDFLib which allows the manipulation of 21 | the input and output using the RDF standard model which is part of the 22 | OSLC specification. The RDFLib was also extended by using other plugins 23 | to implement serializers to convert the output in different formats. 24 | 25 | Authlib for OAuth. 26 | ======================= 27 | There is also a third component that was used to implement the authentication 28 | process and to enable the OAuth workflow required for the integration of an 29 | OSLC API with other applications. 30 | -------------------------------------------------------------------------------- /app/api/adapter/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from flask import Blueprint, request 4 | from flask_restx import Api 5 | 6 | bp = Blueprint('oslc', __name__, url_prefix='/services', static_folder='static') 7 | 8 | api = Api( 9 | app=bp, 10 | version='1.0.0', 11 | title='Python OSLC API', 12 | description='Implementation for the OSLC specification for python application', 13 | contact='Contact Software & Koneksys', 14 | contact_url='https://www.contact-software.com/en/', 15 | contact_email="mario.carrasco@koneksys.com", 16 | validate=True 17 | ) 18 | 19 | 20 | @bp.app_errorhandler(500) 21 | def internal_error(error): 22 | logger = logging.getLogger('flask.app') 23 | logger.debug('Requesting INTERNAL_ERROR from: {}'.format(request.base_url)) 24 | 25 | 26 | @bp.before_request 27 | def before_request_func(): 28 | logger = logging.getLogger('flask.app') 29 | logger.debug('Requesting BEFORE_REQUEST from: {} {} to {}'.format(request.access_route, 30 | request.user_agent, 31 | request.base_url)) 32 | logger.debug('Request Referrer {}'.format(request.referrer)) 33 | 34 | 35 | @api.errorhandler 36 | def default_error_handler(e): 37 | # if not settings.FLASK_DEBUG: 38 | return {'message': e}, 500 39 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/authorize.html: -------------------------------------------------------------------------------- 1 | {% extends "pyoslc_oauth/base.html" %} 2 | {% from "pyoslc_oauth/macros.html" import render_form %} 3 | 4 | {% block form %} 5 |

Authorization

6 |

Client {{ client.name }} is requesting:

7 | {% if scopes is defined and grant.scopes %} 8 | 14 | {% endif %} 15 |
16 | {{ render_form(form) }} 17 | 18 |
19 | {% endblock %} 20 | 21 | 22 | {#
#} 23 | {##} 24 | {#
#} 25 | {# #} 26 | {# #} 27 | {# #} 28 | {#
#} 29 | {##} 30 | {#
#} 31 | {# Authorize {{ req.name }} access to use your account?#} 32 | {# #} 33 | {##} 34 | {# #} 35 | {#
#} 36 | {#
#} 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, CONTACT Software 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/stream.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | {{ stream_id }} 11 | 13 | PyOSLC Initial Stream 14 | 15 | 17 | 18 | 19 | 21 | 22 | Description 23 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /app/api/adapter/mappings/specification.py: -------------------------------------------------------------------------------- 1 | 2 | specification_map = { 3 | # RDF and OSLC attributes 4 | 'Specification_id': {'attribute': '_BaseResource__identifier', 'oslc_property': 'DCTERMS.identifier'}, 5 | 'Title': {'attribute': '_BaseResource__title', 'oslc_property': 'DCTERMS.title'}, 6 | 'Description': {'attribute': '_BaseResource__description', 'oslc_property': 'DCTERMS.description'}, 7 | 'Author': {'attribute': '_BaseResource__creator', 'oslc_property': 'DCTERMS.creator'}, 8 | 9 | # RM and Custom attributes 10 | 'Product': {'attribute': '_BaseResource__short_title', 'oslc_property': 'OSLC.shortTitle'}, 11 | # 'Subject': {'attribute': '_BaseResource__subject', 'oslc_property': 'DCTERMS.subject'}, 12 | 'Source': {'attribute': '_Requirement__elaborated_by', 'oslc_property': 'OSLC_RM.elaboratedBy'}, 13 | 'Category': {'attribute': '_Requirement__constrained_by', 'oslc_property': 'OSLC_RM.constrainedBy'}, 14 | 'Discipline': {'attribute': '_Requirement__satisfied_by', 'oslc_property': 'OSLC_RM.satisfiedBy'}, 15 | 'Revision': {'attribute': '_Requirement__tracked_by', 'oslc_property': 'OSLC_RM.trackedBy'}, 16 | 'Target_Value': {'attribute': '_Requirement__validated_by', 'oslc_property': 'OSLC_RM.validatedBy'}, 17 | 'Degree_of_fulfillment': {'attribute': '_Requirement__affected_by', 'oslc_property': 'OSLC_RM.affectedBy'}, 18 | 'Status': {'attribute': '_Requirement__decomposed_by', 'oslc_property': 'OSLC_RM.decomposedBy'}, 19 | 20 | # CUSTOM attributes 21 | 'PUID': {'attribute': '_Requirement__puid', 'oslc_property': 'OSLC_RM.puid'}, 22 | 'Project': {'attribute': '_BaseResource__subject', 'oslc_property': 'DCTERMS.subject'}, 23 | } 24 | -------------------------------------------------------------------------------- /pyoslc_oauth/forms.py: -------------------------------------------------------------------------------- 1 | from flask import session, g 2 | from six import PY2 3 | 4 | if PY2: 5 | from flask._compat import string_types 6 | else: 7 | string_types = (str,) 8 | from flask_login import login_user 9 | from flask_wtf import FlaskForm 10 | from wtforms import BooleanField, StringField, PasswordField 11 | from wtforms.validators import DataRequired, StopValidation 12 | from wtforms.widgets import HiddenInput 13 | 14 | from pyoslc_oauth.models import User 15 | 16 | 17 | class BaseForm(FlaskForm): 18 | def hidden_fields(self): 19 | for field in self._fields: 20 | if isinstance(field, string_types): 21 | field = getattr(self, field, None) 22 | 23 | if field and isinstance(field.widget, HiddenInput): 24 | yield field 25 | 26 | def visible_fields(self): 27 | for field in self._fields: 28 | if isinstance(field, string_types): 29 | field = getattr(self, field, None) 30 | 31 | if field and not isinstance(field.widget, HiddenInput): 32 | yield field 33 | 34 | 35 | class ConfirmForm(BaseForm): 36 | confirm = BooleanField() 37 | 38 | 39 | class LoginConfirmForm(ConfirmForm): 40 | username = StringField(validators=[DataRequired()]) 41 | password = PasswordField(validators=[DataRequired()]) 42 | 43 | def validate_password(self, field): 44 | username = self.username.data.lower() 45 | user = User.query.filter_by(username=username).first() 46 | if not user or not user.check_password(field.data): 47 | raise StopValidation('Email or password is invalid.') 48 | 49 | if self.confirm.data: 50 | # login(user, False) 51 | login_user(user) 52 | session['sid'] = user.id 53 | session.permanent = False 54 | g.current_user = user 55 | 56 | 57 | class AdminLogin(BaseForm): 58 | pass 59 | -------------------------------------------------------------------------------- /app/api/adapter/namespaces/rm/parsers.py: -------------------------------------------------------------------------------- 1 | from flask_restx import reqparse 2 | from werkzeug.datastructures import FileStorage 3 | 4 | csv_file_upload = reqparse.RequestParser() 5 | csv_file_upload.add_argument('csv_file', type=FileStorage, location='files', required=True, help='CSV File') 6 | 7 | 8 | specification_parser = reqparse.RequestParser() 9 | specification_parser.add_argument('specification_id', type=str, required=True, help='ID of the specification') 10 | specification_parser.add_argument('product', type=str, required=True, help='Name of the product') 11 | specification_parser.add_argument('project', type=str, required=True, help='Name of the project') 12 | specification_parser.add_argument('title', type=str, required=True, help='Title of the specification') 13 | specification_parser.add_argument('description', type=str, required=True, help='Description of the specification') 14 | specification_parser.add_argument('source', type=str, required=True, help='Source of the specification') 15 | specification_parser.add_argument('author', type=str, required=True, help='Author of the specification') 16 | specification_parser.add_argument('category', type=str, required=True, help='Category of the specification') 17 | specification_parser.add_argument('discipline', type=str, required=True, help='Discipline of th specification') 18 | specification_parser.add_argument('revision', type=str, required=True, help='Revision') 19 | specification_parser.add_argument('target_value', type=str, required=True, help='Target value of the specification') 20 | specification_parser.add_argument('degree_of_fulfillment', type=str, required=True, 21 | help='Degree of fulfillment of the specification') 22 | specification_parser.add_argument('status', type=str, required=True, help='Status of the specification') 23 | 24 | specification_id_parser = reqparse.RequestParser() 25 | specification_id_parser.add_argument('id', type=str, help='ID of the specification') 26 | -------------------------------------------------------------------------------- /docs/source/getting_the_code.rst: -------------------------------------------------------------------------------- 1 | Getting the code. 2 | ================= 3 | PyOSLC is available as an open-source project and it could be accessed 4 | through its git repository in the next url. 5 | 6 | ssh: git@gitlab.contact.de:frank/pyoslc.git 7 | 8 | https: https://gitlab.contact.de/frank/pyoslc.git 9 | 10 | It is possible to clone the project using a git client (desktop or command line). 11 | 12 | Knowing the project. 13 | -------------------- 14 | After cloning the project there are some important points to know about 15 | the structure and architecture of the project. 16 | 17 | Let’s see the structure of the project: 18 | 19 | :: 20 | 21 | $ tree pysolc 22 | ├── .env 23 | ├── .flaskenv 24 | ├── LICENSE 25 | ├── MANIFEST.in 26 | ├── README.md 27 | ├── app 28 | ├── examples 29 | ├── initialize.py 30 | ├── pyoslc 31 | ├── pyoslc_oauth 32 | ├── requirements.txt 33 | ├── setup.cfg 34 | └── setup.py 35 | 36 | The structure of the project shows some files and folders that contain 37 | the implementation of the SDK and an example of how to use it for creating 38 | an OSLC API. 39 | 40 | The pyoslc folder. 41 | ^^^^^^^^^^^^^^^^^^ 42 | This folder contains the classes that define the SDK, classes to create 43 | the instances for ServiceProvider, Service, QueryCapability and the other 44 | components for an OSLC API. 45 | 46 | The pysocl_oauth folder. 47 | ^^^^^^^^^^^^^^^^^^^^^^^^ 48 | This folder contains the classes and the implementation of the authentication 49 | and the authorization process to an OSLC API, it also includes the implementation 50 | of the OAuth workflow required for other OSLC API which PyOSLC could interact with. 51 | 52 | The app folder. 53 | ^^^^^^^^^^^^^^^ 54 | This is the implementation of the PyOSLC SDK, is an example of how to use 55 | or implement the PyOSLC SDK for creating an OSLC API, contains the implementation 56 | of the REST-based API and all the services for exposing the information 57 | using the SDK. 58 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/scr.html: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | http://192.168.1.66:5000/oslc/hereistheservice 22 | 23 | 24 | 25 | http://open-services.net/ns/rm# 26 | 27 | false 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from logging.handlers import RotatingFileHandler 4 | 5 | from flask import Flask 6 | 7 | 8 | def create_app(app_config=None): 9 | app = Flask(__name__, instance_relative_config=False) 10 | 11 | if app_config is None: 12 | # load the instance config, if it exists, when not testing 13 | app.config.from_pyfile('config.py', silent=True) 14 | else: 15 | # load the test config if passed in 16 | app.config.from_object(app_config) 17 | 18 | # ensure the instance folder exists 19 | try: 20 | os.makedirs(app.instance_path) 21 | except OSError: 22 | pass 23 | 24 | if not app.debug and not app.testing: 25 | if app.config['MAIL_SERVER']: 26 | """ 27 | Implementation for Mail notifications 28 | """ 29 | pass 30 | 31 | if app.debug and app.config['LOG_TO_STDOUT']: 32 | stream_handler = logging.StreamHandler() 33 | stream_handler.setLevel(logging.INFO) 34 | app.logger.addHandler(stream_handler) 35 | else: 36 | if not os.path.exists('logs'): 37 | os.mkdir('logs') 38 | 39 | file_handler = RotatingFileHandler('logs/pysolc.log', maxBytes=10240, backupCount=10) 40 | file_handler.setFormatter(logging.Formatter('%(asctime)s ' 41 | '%(levelname)s: %(message)s ' 42 | '[in %(pathname)s:%(lineno)d]')) 43 | file_handler.setLevel(logging.INFO) 44 | app.logger.addHandler(file_handler) 45 | 46 | app.logger.setLevel(logging.INFO) 47 | app.logger.info('---------- Initializing PyOSL WS-API ----------') 48 | 49 | from .web.routes import bp as website 50 | app.register_blueprint(website) 51 | 52 | from app.api.adapter import oslc 53 | oslc.init_app(app) 54 | 55 | from app.api.oauth import oslc_oauth 56 | oslc_oauth.init_app(app) 57 | 58 | return app 59 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open('README.md', 'r') as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name='pyoslc', 8 | version='0.1.0', 9 | author='Contact Software', 10 | author_email='fp@contact.de', 11 | description='SDK for implementing OSLC API using Python.', 12 | long_description=long_description, 13 | long_description_content_type='text/markdown', 14 | url='https://github.com/cslab/pyoslc', 15 | packages=find_packages(), 16 | classifiers=[ 17 | 'Intended Audience :: Developers', 18 | 'Topic :: Software Development :: Build Tools', 19 | 'Programming Language :: Python :: 2.7', 20 | 'Programming Language :: Python :: 3', 21 | 'License :: OSI Approved', 22 | 'Operating System :: OS Independent', 23 | ], 24 | keywords='OSLC, SDK, REST, API, RDF, JSON-LD', 25 | python_requires='>=2.7', 26 | install_requires=[ 27 | "python-dotenv == 0.18.0 ; python_version <= '2.7'", 28 | "python-dotenv > 0.18.0 ; python_version > '2.7'", 29 | "RDFLib == 5.0.0 ; python_version <= '2.7'", 30 | "RDFLib-JSONLD == 0.6.1 ; python_version <= '2.7'", 31 | "RDFLib >= 6.0.0 ; python_version > '2.7'", 32 | "Flask == 1.0.2 ; python_version <= '2.7'", 33 | "Flask > 1.0.2 ; python_version > '2.7'", 34 | "Flask-RESTx == 0.5.1 ; python_version <= '2.7'", 35 | "Flask-RESTx > 0.5.1 ; python_version > '2.7'", 36 | 'authlib~=0.14.3', 37 | 'cachelib~=0.1.1', 38 | 'requests', 39 | "Werkzeug == 1.0.1 ; python_version <= '2.7'", 40 | "Werkzeug > 1.0.1 ; python_version > '2.7'", 41 | ], 42 | extras_require={ 43 | 'dotenv': ['python-dotenv'], 44 | 'dev': ['check-manifest'], 45 | 'test': ['pytest', 'pytest-cov', 'pytest-html'], 46 | }, 47 | project_urls={ 48 | 'Bug Reports': 'https://github.com/cslab/pyoslc/issues', 49 | 'Source': 'https://github.com/cslab/pyoslc', 50 | }, 51 | ) 52 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Testing the Adapter 5 | 6 | on: [push] 7 | 8 | jobs: 9 | tests: 10 | name: test 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | python: [2.7, 3.7, 3.8, 3.9] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ matrix.python }} 23 | 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install setuptools wheel twine 28 | 29 | 30 | - name: Install requirements 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install -r requirements.txt 34 | 35 | - name: Lint with flake8 36 | run: | 37 | pip install flake8 38 | flake8 . 39 | 40 | - name: Install Tox and any other packages 41 | run: | 42 | pip install tox 43 | 44 | - name: Run Tox 45 | # Run tox using the version of Python in `PATH` 46 | run: | 47 | tox -e py 48 | 49 | # deploy: 50 | # 51 | # runs-on: ubuntu-latest 52 | # 53 | # steps: 54 | # - uses: actions/checkout@v2 55 | # - name: Set up Python 56 | # uses: actions/setup-python@v2 57 | # with: 58 | # python-version: ${{ matrix.python }} 59 | # - name: Install dependencies 60 | # run: | 61 | # python -m pip install --upgrade pip 62 | # pip install setuptools wheel twine 63 | # - name: Build and publish 64 | # env: 65 | # TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 66 | # TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 67 | # run: | 68 | # python setup.py sdist bdist_wheel 69 | # twine upload dist/* -------------------------------------------------------------------------------- /app/api/adapter/dialogs/static/css/adaptor.css: -------------------------------------------------------------------------------- 1 | /* Start of user code "Copyright" 2 | */ 3 | /******************************************************************************* 4 | Copyright (c) 2017 KTH Royal Institute of Technology. 5 | 6 | All rights reserved. This program and the accompanying materials 7 | are made available under the terms of the Eclipse Public License v1.0 8 | and Eclipse Distribution License v. 1.0 which accompanies this distribution. 9 | 10 | The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html 11 | and the Eclipse Distribution License is available at 12 | http://www.eclipse.org/org/documents/edl-v10.php. 13 | 14 | Contributors: 15 | 16 | Fr�d�ric Loiret - Switch the template to Bootstrap (519699) 17 | Andrii Berezovskyi - Support for UI Preview (494303) 18 | 19 | This file is generated by org.eclipse.lyo.oslc4j.codegenerator 20 | *******************************************************************************/ 21 | /* End of user code */ 22 | 23 | /* Sticky footer 24 | -------------------------------------------------- */ 25 | html { 26 | position: relative; 27 | min-height: 100%; 28 | } 29 | body { 30 | /* Margin bottom by footer height */ 31 | margin-bottom: 60px; 32 | } 33 | .footer { 34 | position: absolute; 35 | bottom: 0; 36 | width: 100%; 37 | /* Set the fixed height of the footer here */ 38 | height: 60px; 39 | background-color: #f5f5f5; 40 | } 41 | 42 | 43 | /* Custom adaptor CSS 44 | -------------------------------------------------- */ 45 | 46 | body > .container { 47 | padding-top: 20px; 48 | padding-bottom: 10px; 49 | } 50 | .container .text-muted { 51 | margin: 20px 0; 52 | } 53 | 54 | .footer > .container { 55 | padding-right: 15px; 56 | padding-left: 15px; 57 | } 58 | 59 | code { 60 | font-size: 80%; 61 | } 62 | 63 | /* 45/11em are the dialog sizes plus bootstrap margins */ 64 | .popover { 65 | max-width: calc(45em + 30px); 66 | max-height: calc(11em + 50px); 67 | } 68 | 69 | #delegatedUI { 70 | width: 523px; 71 | height: 320px; /*this is selection; creation has 480px*/ 72 | } 73 | -------------------------------------------------------------------------------- /app/web/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% block title %}{% endblock %} - PyOSLC 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |

PyOSLC

20 |

An OSLC adapter implemented on Python.

21 |
22 |

For showing the REST API implementation for the OSLC adapter click the next button.

23 | OSLC API 24 | {% block button_home %}{% endblock %} 25 |
26 | {% block header %}{% endblock %} 27 |
28 | 29 | {% block content %}{% endblock %} 30 | 31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/api/adapter/namespaces/rm/models.py: -------------------------------------------------------------------------------- 1 | from flask_restx import fields 2 | 3 | from app.api.adapter import api 4 | 5 | base_requirement = api.model('Base Requirement', { 6 | 'title': fields.String, 7 | 'description': fields.String, 8 | 'identifier': fields.String, 9 | 'short_title': fields.String, 10 | 'subject': fields.String, 11 | 'creator': fields.String, # fields.Url('example'), 12 | 'contributor': fields.String, # fields.Url('example'), 13 | 'created': fields.DateTime, 14 | 'modified': fields.DateTime, 15 | 'type': fields.String, # fields.Url('example'), 16 | 'service_provider': fields.String, # fields.Url('example'), 17 | 'instance_shape': fields.String, # fields.Url('example') 18 | }) 19 | 20 | requirement = api.inherit('Requirement', base_requirement, { 21 | 'elaborated_by': fields.String, # fields.Url('example'), 22 | 'elaborates': fields.String, # fields.Url('example'), 23 | 'specified_by': fields.String, # fields.Url('example'), 24 | 'specifies': fields.String, # fields.Url('example'), 25 | 'affected_by': fields.String, # fields.Url('example'), 26 | 'tracked_by': fields.String, # fields.Url('example'), 27 | 'implemented_by': fields.String, # fields.Url('example'), 28 | 'validated_by': fields.String, # fields.Url('example'), 29 | 'satisfied_by': fields.String, # fields.Url('example'), 30 | 'satisfies': fields.String, # fields.Url('example'), 31 | 'decomposed_by': fields.String, # fields.Url('example'), 32 | 'decomposes': fields.String, # fields.Url('example'), 33 | 'constrained_by': fields.String, # fields.Url('example'), 34 | 'constrains': fields.String, # fields.Url('example') 35 | }) 36 | 37 | specification = api.model('Specification', { 38 | 'specification_id': fields.String, 39 | 'product': fields.String, 40 | 'project': fields.String, 41 | 'subject': fields.String, 42 | 'title': fields.String, 43 | 'description': fields.String, 44 | 'source': fields.String, 45 | 'author': fields.String, 46 | 'category': fields.String, 47 | 'discipline': fields.String, 48 | 'revision': fields.String, 49 | 'target_value': fields.String, 50 | 'degree_of_fulfillment': fields.String, 51 | 'status': fields.String 52 | }) 53 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/selection.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Configuratin Selection Dialog 9 | 10 | 11 | 12 | 24 | 25 | 26 | 27 |
28 | Type: 29 | 33 | 34 |

Click on Load to retrieve the Streams from PyOSLC-Adaptor.

35 | 36 | 37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | 49 | 52 |
53 | 54 |
55 | 56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /app/api/adapter/services/factories.py: -------------------------------------------------------------------------------- 1 | from app.api.adapter.resources.resource_service import get_service_resources 2 | from app.api.adapter.services.specification import ServiceResource 3 | from pyoslc.resources.factories import ServiceProviderFactory, ConfigurationFactory 4 | from pyoslc.vocabularies.jazz import JAZZ_CONFIG 5 | 6 | 7 | class ContactServiceProviderFactory(object): 8 | 9 | @classmethod 10 | def create_service_provider(cls, base_uri, title, description, publisher, parameters): 11 | classes = get_service_resources(ServiceResource) 12 | 13 | sp = ServiceProviderFactory.create_service_provider(base_uri, title, description, 14 | publisher, classes, parameters) 15 | 16 | sp.add_detail(base_uri) 17 | 18 | prefix_definitions = list() 19 | # prefix_definitions.append(PrefixDefinition(prefix='dcterms', prefix_base=DCTERMS)) 20 | # prefix_definitions.append(PrefixDefinition(prefix='oslc', prefix_base=OSLC)) 21 | # prefix_definitions.append(PrefixDefinition(prefix='oslc_data', prefix_base=OSLCData)) 22 | # prefix_definitions.append(PrefixDefinition(prefix='rdf', prefix_base=RDF)) 23 | # prefix_definitions.append(PrefixDefinition(prefix='rdfs', prefix_base=RDFS)) 24 | # prefix_definitions.append(PrefixDefinition(prefix='oslc_am', prefix_base=OSLC_AM)) 25 | # prefix_definitions.append(PrefixDefinition(prefix='oslc_cm', prefix_base=OSLC_CM)) 26 | # prefix_definitions.append(PrefixDefinition(prefix='oslc_rm', prefix_base=OSLC_RM)) 27 | 28 | sp.prefix_definition = prefix_definitions 29 | 30 | return sp 31 | 32 | 33 | class ContactConfigurationFactory(object): 34 | 35 | @classmethod 36 | def create_components(cls, base_uri, title, description, publisher, parameters): 37 | 38 | resource_attributes = { 39 | 'title': 'Configuration Picker', 40 | 'label': 'Selection Component', 41 | 'uri': 'selection', 42 | 'hint_width': '600px', 43 | 'hint_height': '500px', 44 | 'resource_type': [JAZZ_CONFIG.Configuration], 45 | 'usages': [JAZZ_CONFIG.Configuration] 46 | } 47 | 48 | component = ConfigurationFactory.create_component(base_uri, title, description, 49 | publisher, resource_attributes, parameters) 50 | return component 51 | -------------------------------------------------------------------------------- /pyoslc_oauth/routes/oauth.py: -------------------------------------------------------------------------------- 1 | from authlib.oauth1.rfc5849.errors import OAuth1Error 2 | from flask import Blueprint, current_app, request, jsonify, render_template 3 | from flask_login import current_user 4 | 5 | from pyoslc_oauth import login 6 | from pyoslc_oauth.forms import ConfirmForm, LoginConfirmForm 7 | from pyoslc_oauth.models import User, Client 8 | from pyoslc_oauth.resources import OAuthConfiguration 9 | from pyoslc_oauth.server import auth_server 10 | 11 | oauth_bp = Blueprint('oauth', __name__, 12 | template_folder='../templates', 13 | static_folder='../static') 14 | 15 | 16 | @login.user_loader 17 | def load_user(id): 18 | return User.query.get(int(id)) 19 | 20 | 21 | @oauth_bp.route('/initiate', methods=['POST']) 22 | def initiate_temporary_credential(): 23 | current_app.logger.debug('Creating temporary credentials for the consumer') 24 | return auth_server.create_temporary_credentials_response() 25 | 26 | 27 | @oauth_bp.route('/authorize', methods=['GET', 'POST']) 28 | def authorize(): 29 | if current_user.is_authenticated: 30 | form = ConfirmForm() 31 | else: 32 | form = LoginConfirmForm() 33 | 34 | if form.validate_on_submit(): 35 | if form.confirm.data: 36 | grant_user = current_user 37 | else: 38 | username = form.username.data.lower() 39 | password = form.password.data 40 | 41 | auth_conf = OAuthConfiguration.get_instance() 42 | auth_app = auth_conf.application 43 | auth_app.login(username=username, password=password) 44 | 45 | grant_user = current_user 46 | 47 | return auth_server.create_authorization_response(request, grant_user) 48 | 49 | try: 50 | grant = auth_server.check_authorization_request() 51 | except OAuth1Error as error: 52 | # TODO: add an error page 53 | payload = dict(error.get_body()) 54 | # return render_template('error.html', error=error) 55 | return jsonify(payload), error.status_code 56 | 57 | credential = grant.credential 58 | client_id = credential.get_client_id() 59 | client = Client.query.filter_by(client_id=client_id).first() 60 | return render_template( 61 | 'pyoslc_oauth/authorize.html', 62 | grant=grant, 63 | client=client, 64 | form=form, 65 | ) 66 | 67 | 68 | @oauth_bp.route('/token', methods=['POST']) 69 | def issue_token(): 70 | return auth_server.create_token_response() 71 | -------------------------------------------------------------------------------- /pyoslc/vocabularies/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Vocabulary definition for the OSLC specification. 3 | 4 | Taken from: 5 | http://docs.oasis-open.org/oslc-core/oslc-core/v3.0/csprd03/part7-core-vocabulary/oslc-core-v3.0-csprd03-part7-core-vocabulary.html#rdfvocab 6 | http://docs.oasis-open.org/oslc-core/oslc-core/v3.0/csprd03/part7-core-vocabulary/oslc-core-v3.0-csprd03-part7-core-vocabulary.html#vocabulary-details 7 | 8 | """ 9 | from rdflib import URIRef 10 | from rdflib.namespace import ClosedNamespace 11 | 12 | OSLC = ClosedNamespace( 13 | uri=URIRef("http://open-services.net/ns/core#"), 14 | terms=[ 15 | # RDFS Classes in this namespace 16 | "AllowedValues", "AttachmentContainer", "AttachmentDescriptor", 17 | "Comment", "Compact", "CreationFactory", "Dialog", 18 | "Discussion", "Error", "ExtendedError", "OAuthConfiguration", 19 | "PrefixDefinition", "Preview", "Property", "Publisher", 20 | "QueryCapability", "ResourceShape", "ResponseInfo", 21 | "Service", "ServiceProvider", "ServiceProviderCatalog", 22 | "Compact", "Preview", 23 | 24 | # RDF Properties in this namespace 25 | "allowedValue", "allowedValues", "archived", "attachment", "attachmentSize", 26 | "authorizationURI", "comment", "creation", "creationDialog", "creationFactory", 27 | "default", "defaultValue", "describes", "details", "dialog", "discussedBy", 28 | "discussionAbout", "document", "domain", "error", "executes", "extendedError", 29 | "futureAction", "hidden", "hintHeight", "hintWidth", "icon", "iconAltLabel", 30 | "iconSrcSet", "iconTitle", "impactType", "initialHeight", "inReplyTo", 31 | "instanceShape", "inverseLabel", "isMemberProperty", "label", "largePreview", 32 | "maxSize", "message", "modifiedBy", "moreInfo", "name", "nextPage", 33 | "oauthAccessTokenURI", "oauthConfiguration", "oauthRequestTokenURI", "occurs", 34 | "partOfDiscussion", "postBody", "prefix", "prefixBase", "prefixDefinition", 35 | "property", "propertyDefinition", "queryable", "queryBase", "queryCapability", 36 | "range", "readOnly", "rel", "representation", "resourceShape", "resourceType", 37 | "results", "selectionDialog", "service", "serviceProvider", "serviceProviderCatalog", 38 | "shortId", "shortTitle", "smallPreview", "statusCode", "totalCount", "usage", 39 | "valueShape", "valueType", "publisher", 40 | "document", "hintHeight", "hintWidth", "initialHeight", "icon", 41 | "smallPreview", "largePreview", 42 | ] 43 | ) 44 | -------------------------------------------------------------------------------- /pyoslc_oauth/models.py: -------------------------------------------------------------------------------- 1 | from authlib.integrations.sqla_oauth1 import OAuth1ClientMixin, OAuth1TokenCredentialMixin 2 | from flask import current_app, g 3 | from flask_login import UserMixin 4 | try: 5 | from werkzeug.contrib.cache import FileSystemCache 6 | except ImportError: 7 | from cachelib import FileSystemCache 8 | from werkzeug.local import LocalProxy 9 | from werkzeug.security import generate_password_hash, check_password_hash 10 | 11 | from pyoslc_oauth.database import db 12 | 13 | 14 | class User(UserMixin, db.Model): 15 | id = db.Column(db.Integer, primary_key=True) 16 | username = db.Column(db.String(40), unique=True, nullable=False) 17 | _password = db.Column('password', db.String(100)) 18 | 19 | def get_user_id(self): 20 | return self.id 21 | 22 | @property 23 | def password(self): 24 | return self._password 25 | 26 | @password.setter 27 | def password(self, raw): 28 | self._password = generate_password_hash(raw) 29 | 30 | def check_password(self, raw): 31 | if not self._password: 32 | return False 33 | return check_password_hash(self._password, raw) 34 | 35 | def to_dict(self): 36 | return dict(id=self.id, username=self.username) 37 | 38 | 39 | class Client(db.Model, OAuth1ClientMixin): 40 | id = db.Column(db.Integer, primary_key=True) 41 | name = db.Column(db.String(48), nullable=False) 42 | 43 | # This section could be overwritten by the implementer 44 | # to link the Client with a functional user 45 | # 46 | # user_id = db.Column( 47 | # db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') 48 | # ) 49 | # user = db.relationship('User') 50 | 51 | def __init__(self, name, client_id, client_secret, default_redirect_uri): 52 | self.name = name 53 | self.client_id = client_id 54 | self.client_secret = client_secret 55 | self.default_redirect_uri = default_redirect_uri 56 | 57 | 58 | class TokenCredential(db.Model, OAuth1TokenCredentialMixin): 59 | id = db.Column(db.Integer, primary_key=True) 60 | user_id = db.Column( 61 | db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') 62 | ) 63 | user = db.relationship('User') 64 | 65 | def set_user_id(self, user_id): 66 | self.user_id = user_id 67 | 68 | 69 | def _get_cache(): 70 | _cache = g.get('_oauth_cache') 71 | if _cache: 72 | return _cache 73 | _cache = FileSystemCache(current_app.config['OAUTH_CACHE_DIR']) 74 | g._oauth_cache = _cache 75 | return _cache 76 | 77 | 78 | cache = LocalProxy(_get_cache) 79 | -------------------------------------------------------------------------------- /pyoslc_oauth/templates/pyoslc_oauth/manage_consumer.html: -------------------------------------------------------------------------------- 1 | {% extends "pyoslc_oauth/base.html" %} 2 | 3 | {% block content %} 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for consumer in consumers.provisional %} 16 | 17 | 24 | 25 | 26 | 34 | 35 | {% endfor %} 36 | 37 |
NameKeyTrustedAction
18 | {% if consumer.provisional %} 19 | 20 | {% else %} 21 | {{ consumer.name }} 22 | {% endif %} 23 | {{ consumer.key }}{{ consumer.trusted }} 27 | {% if consumer.provisional %} 28 | 29 | 30 | {% else %} 31 | 32 | {% endif %} 33 |
38 |
39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {% for consumer in consumers.approved %} 52 | 53 | 60 | 61 | 62 | 70 | 71 | {% endfor %} 72 | 73 |
NameKeyTrustedAction
54 | {% if consumer.provisional %} 55 | 56 | {% else %} 57 | {{ consumer.name }} 58 | {% endif %} 59 | {{ consumer.key }}{{ consumer.trusted }} 63 | {% if consumer.provisional %} 64 | 65 | 66 | {% else %} 67 | 68 | {% endif %} 69 |
74 |
75 | 76 | {% endblock %} -------------------------------------------------------------------------------- /tests/functional/test_web.py: -------------------------------------------------------------------------------- 1 | def test_index(pyoslc): 2 | """ 3 | GIVEN The PyOSLC API 4 | WHEN retrieving the root path 5 | THEN it should return the home page with the links to 6 | the swagger application with the /oslc path 7 | """ 8 | response = pyoslc.get_swagger() 9 | assert b"PyOSLC" in response.data 10 | assert b"An OSLC adapter implemented on Python." in response.data 11 | assert b"For showing the REST API implementation for the OSLC adapter click the next button." in response.data 12 | assert b'href="/"' in response.data 13 | assert b'href="/oslc/"' in response.data 14 | 15 | 16 | def test_oslc(pyoslc): 17 | """ 18 | GIVEN The PyOSLC API that includes the swagger library 19 | WHEN requesting the oslc path 20 | THEN the swagger.json elements should be validated 21 | """ 22 | response = pyoslc.get_swagger('/oslc/') 23 | assert response.status_code == 200 24 | assert b'swagger.json' in response.data 25 | 26 | 27 | def test_list_requirement(client): 28 | """ 29 | Testing the feature for listing of the requirements 30 | showed on the page 31 | """ 32 | headers = { 33 | 'Content-Type': 'text/html', 34 | 'Accept': 'text/html' 35 | } 36 | response = client.get('oslc/rm/requirement', headers=headers) 37 | assert b'Listing the Requirements.' in response.data 38 | assert b'http://localhost/oslc/rm/requirement/X1C2V3B4' in response.data 39 | 40 | 41 | def test_requirement(client): 42 | """ 43 | Testing the information showed for a specific requirement 44 | """ 45 | headers = { 46 | 'Content-Type': 'text/html', 47 | 'Accept': 'text/html' 48 | } 49 | response = client.get('oslc/rm/requirement/X1C2V3B4', headers=headers) 50 | assert b'http://purl.org/dc/terms/identifier' in response.data 51 | assert b'X1C2V3B4' in response.data 52 | assert b'http://purl.org/dc/terms/title' in response.data 53 | assert b'OSLC RM Spec 4' in response.data 54 | assert b'http://open-services.net/ns/core#shortTitle' in response.data 55 | assert b'SDK-Dev' in response.data 56 | assert b'http://purl.org/dc/terms/subject' in response.data 57 | assert b'OSLC RM Spec 4' in response.data 58 | assert b'http://purl.org/dc/terms/description' in response.data 59 | assert b'The OSLC RM Specification needs to be awesome 4' in response.data 60 | assert b'http://open-services.net/ns/rm#constrainedBy' in response.data 61 | assert b'Customer Requirement' in response.data 62 | assert b'http://open-services.net/ns/rm#satisfiedBy' in response.data 63 | assert b'Software Development' in response.data 64 | -------------------------------------------------------------------------------- /app/api/adapter/dialogs/templates/dialogs/creator.html: -------------------------------------------------------------------------------- 1 | {% import "bootstrap/wtf.html" as wtf %} 2 | 3 | 4 | 5 | 6 | ResourceCreationDialog 7 | 8 | 10 | 11 | 12 | 28 | 29 | 30 | Type: 31 | 34 |
35 |
36 |
37 | {{ form.hidden_tag() }} 38 | {{ wtf.form_errors(form, hiddens="only") }} 39 | 40 | {{ wtf.form_field(form.specification_id, form_type="horizontal", extra_classes='from-control-sm') }} 41 | {{ wtf.form_field(form.title, form_type="horizontal", horizontal_columns=('sm', 2, 10)) }} 42 | {{ wtf.form_field(form.description, form_type="horizontal", horizontal_columns=('sm', 2, 10)) }} 43 | {{ wtf.form_field(form.author, form_type="horizontal", horizontal_columns=('sm', 2, 10)) }} 44 | {{ wtf.form_field(form.product, form_type="horizontal", horizontal_columns=('sm', 2, 10)) }} 45 | {{ wtf.form_field(form.subject, form_type="horizontal", horizontal_columns=('sm', 2, 10)) }} 46 | {{ wtf.form_field(form.source, form_type="horizontal", horizontal_columns=('sm', 2, 10)) }} 47 | {{ wtf.form_field(form.category, form_type="horizontal", horizontal_columns=('sm', 2, 10)) }} 48 | 49 | 52 | 53 |
54 |
55 |
56 | 57 | -------------------------------------------------------------------------------- /app/api/adapter/dialogs/routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, render_template, request, jsonify, make_response 2 | 3 | from app.api.adapter.forms import SpecificationForm, SelectSpecificationForm 4 | from app.api.adapter.namespaces.business import get_requirements, create_requirement 5 | 6 | dialog_bp = Blueprint('dialog', __name__, template_folder='templates', static_folder='static') 7 | 8 | 9 | @dialog_bp.route('/provider//resources/selector', methods=['GET', 'POST']) 10 | def index(service_provider_id): 11 | selection_url = request.base_url 12 | selection_type_url = selection_url.replace('selector', 'types') 13 | 14 | form = SelectSpecificationForm() 15 | list_req = None 16 | 17 | resource_type = request.args.get('type') 18 | if resource_type: 19 | requirements = get_requirements(selection_url) 20 | terms = request.args.get('terms', None) 21 | 22 | results = list() 23 | for r in requirements: 24 | if terms != '': 25 | if r.identifier.__contains__(terms) or r.title.__contains__(terms) or r.description.__contains__(terms): 26 | results.append({ 27 | 'oslc:label': r.identifier + ' / ' + r.title, 28 | 'rdf:resource': str(r.about) 29 | }) 30 | else: 31 | results.append({ 32 | 'oslc:label': r.identifier + ' / ' + r.title, 33 | 'rdf:resource': str(r.about) 34 | }) 35 | 36 | return jsonify({'oslc:results': results}) 37 | 38 | return render_template("dialogs/selector.html", selection_url=selection_url, 39 | selection_type_url=selection_type_url, form=form, list_req=list_req) 40 | 41 | 42 | @dialog_bp.route('/provider//resources/creator', methods=['GET', 'POST']) 43 | def create(service_provider_id): 44 | creator_url = request.base_url 45 | form = SpecificationForm() 46 | 47 | if form.validate_on_submit(): 48 | 49 | req = create_requirement(form.data) 50 | 51 | results = [{ 52 | 'rdf:resource': creator_url.replace('creator', 'requirement') + '/' + req.identifier, 53 | 'oslc:label': req.identifier 54 | }] 55 | 56 | response = make_response(jsonify({"oslc:results": results}), 201) 57 | 58 | response.headers['Content-Type'] = 'application/rdf+xml; charset=UTF-8' 59 | response.headers['OSLC-Core-Version'] = "2.0" 60 | response.headers['Location'] = creator_url + '/' + req.identifier 61 | 62 | return response 63 | 64 | return render_template("dialogs/creator.html", creator_url=creator_url, form=form) 65 | 66 | 67 | @dialog_bp.route('/provider//resources/types', methods=['GET']) 68 | def types(service_provider_id): 69 | results = list() 70 | results.append({ 71 | 'oslc:label': 'Specification', 72 | 'rdf:resource': str('specification') 73 | }) 74 | 75 | return jsonify({'oslc:results': results}) 76 | -------------------------------------------------------------------------------- /app/api/adapter/dialogs/templates/dialogs/selector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Selection Dialog 6 | 7 | 8 | 20 | 21 | 22 | 23 |
24 | Type: 25 | 28 | 29 |

Find a specific resource through a full-text search.

30 | 31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 |
43 | 46 | 49 |
50 |
51 |
52 | 53 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /pyoslc/rest/resource.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from flask import request, make_response 4 | from flask_restx import Resource 5 | from rdflib import Graph, RDF, RDFS, DCTERMS 6 | from rdflib.plugin import PluginException 7 | from werkzeug.exceptions import UnsupportedMediaType 8 | 9 | from pyoslc.vocabularies.core import OSLC 10 | from pyoslc.vocabularies.jazz import JAZZ_PROCESS 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class OslcResource(Resource): 16 | 17 | def __init__(self, *args, **kwargs): 18 | super(OslcResource, self).__init__(*args, **kwargs) 19 | 20 | self.graph = kwargs.get('graph', Graph()) 21 | self.graph.bind('oslc', OSLC) 22 | self.graph.bind('rdf', RDF) 23 | self.graph.bind('rdfs', RDFS) 24 | self.graph.bind('dcterms', DCTERMS) 25 | self.graph.bind('j.0', JAZZ_PROCESS) 26 | 27 | def get(self, *args, **kwargs): 28 | accept = request.headers.get('accept') 29 | logger.debug("accept: {}".format(accept)) 30 | print("accept " + accept) 31 | if not (accept in ('application/rdf+xml', 'application/json', 32 | 'application/ld+json', 'application/json-ld', 33 | 'application/xml', 'application/atom+xml', 34 | 'text/turtle', 35 | 'application/xml, application/x-oslc-cm-service-description+xml', 36 | 'application/x-oslc-compact+xml, application/x-jazz-compact-rendering; q=0.5', 37 | 'application/rdf+xml,application/x-turtle,application/ntriples,application/json')): 38 | print("unsupported media type") 39 | raise UnsupportedMediaType 40 | 41 | @staticmethod 42 | def create_response(graph, accept=None, content=None, rdf_format=None, etag=False): 43 | 44 | # Getting the content-type for checking the 45 | # response we will use to serialize the RDF response. 46 | accept = accept if accept is not None else request.headers.get('accept', 'application/rdf+xml') 47 | content = content if content is not None else request.headers.get('content-type', accept) 48 | if content.__contains__('x-www-form-urlencoded') or content.__contains__('text/plain'): 49 | content = accept 50 | 51 | rdf_format = accept if rdf_format is None else rdf_format 52 | 53 | if accept in ('application/json-ld', 'application/ld+json', 'application/json', '*/*'): 54 | # If the content-type is any kind of json, 55 | # we will use the json-ld format for the response. 56 | rdf_format = 'json-ld' 57 | 58 | # if rdf_format in 'config-xml': 59 | # rdf_format = 'config-xml' 60 | # else: 61 | # rdf_format = 'pretty-xml' 62 | 63 | if rdf_format in ('application/xml', 'application/rdf+xml'): 64 | rdf_format = 'pretty-xml' 65 | 66 | if rdf_format.__contains__('rootservices-xml') and (not accept.__contains__('xml')): 67 | rdf_format = accept 68 | 69 | if rdf_format == 'application/atom+xml': 70 | rdf_format = 'pretty-xml' 71 | 72 | if rdf_format in ('application/xml, application/x-oslc-cm-service-description+xml'): 73 | rdf_format = 'pretty-xml' 74 | content = 'application/rdf+xml' 75 | 76 | try: 77 | logger.debug('Parsing the Graph into {}'.format(rdf_format)) 78 | data = graph.serialize(format=rdf_format) 79 | except PluginException as pe: 80 | response_object = { 81 | 'status': 'fail', 82 | 'message': 'Content-Type Incompatible: {}'.format(pe) 83 | } 84 | return response_object, 400 85 | 86 | # Sending the response to the client 87 | response = make_response(data.decode('utf-8') if not isinstance(data, str) else data, 200) 88 | response.headers['Accept'] = accept 89 | response.headers['Content-Type'] = content 90 | response.headers['OSLC-Core-Version'] = "2.0" 91 | 92 | if etag: 93 | response.add_etag() 94 | 95 | return response 96 | -------------------------------------------------------------------------------- /app/api/adapter/dialogs/static/js/preview.js: -------------------------------------------------------------------------------- 1 | // Start of user code "Copyright Header" 2 | /******************************************************************************* 3 | Copyright (c) 2017 KTH Royal Institute of Technology. 4 | 5 | All rights reserved. This program and the accompanying materials 6 | are made available under the terms of the Eclipse Public License v1.0 7 | and Eclipse Distribution License v. 1.0 which accompanies this distribution. 8 | 9 | The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html 10 | and the Eclipse Distribution License is available at 11 | http://www.eclipse.org/org/documents/edl-v10.php. 12 | 13 | Contributors: 14 | 15 | Fr�d�ric Loiret - Switch the template to Bootstrap (519699) 16 | Andrii Berezovskyi - Support for UI Preview (494303) 17 | 18 | This file is generated by org.eclipse.lyo.oslc4j.codegenerator 19 | *******************************************************************************/ 20 | // End of user code 21 | 22 | /* parse OSLC UI Preview XML into JSON structure with uri, title, h and w */ 23 | function parsePreview(xml) { 24 | var ret = {}; 25 | var compact = firstChild(firstChild(xml)); 26 | 27 | var titleChild = firstChildNamed(compact, 'dcterms:title'); 28 | ret.title = titleChild.textContent; 29 | 30 | var smallPrev = firstChildNamed(compact, 'oslc:smallPreview'); 31 | var largePrev = firstChildNamed(compact, 'oslc:largePreview'); 32 | var preview; 33 | if (smallPrev !== null) { 34 | preview = firstChild(smallPrev); 35 | } else { 36 | preview = firstChild(largePrev); 37 | } 38 | if (preview) { 39 | var document = firstChildNamed(preview, 'oslc:document'); 40 | if (document) ret.uri = document.getAttribute('rdf:resource'); 41 | var height = firstChildNamed(preview, 'oslc:hintHeight'); 42 | ret.height = height.textContent; 43 | var width = firstChildNamed(preview, 'oslc:hintWidth'); 44 | ret.width = width.textContent; 45 | } 46 | return ret; 47 | } 48 | 49 | function firstChild(e) { 50 | for (i = 0; i < e.childNodes.length; i++) { 51 | if (e.childNodes[i].nodeType === Node.ELEMENT_NODE) { 52 | return e.childNodes[i]; 53 | } 54 | } 55 | } 56 | 57 | function firstChildNamed(e, nodeName) { 58 | for (i = 0; i < e.childNodes.length; i++) { 59 | if (e.childNodes[i].nodeType === Node.ELEMENT_NODE 60 | && e.childNodes[i].nodeName === nodeName) { 61 | return e.childNodes[i]; 62 | } 63 | } 64 | } 65 | 66 | $(function () { 67 | 68 | var previewLinks = $("a.oslc-resource-link"); 69 | previewLinks.popover({ 70 | container: "body", 71 | content: "Loading...", 72 | delay: {"show": 120, "hide": 60}, 73 | html: true, 74 | placement: "auto", 75 | trigger: "hover" 76 | }); 77 | 78 | 79 | previewLinks.on("show.bs.popover", function () { 80 | var uiElem = $(this); 81 | var popoverElem = uiElem.data('bs.popover'); 82 | 83 | $.ajax({ 84 | type: "GET", 85 | url: this.getAttribute("href"), 86 | dataType: "xml", 87 | accepts: { 88 | xml: "application/x-oslc-compact+xml" 89 | }, 90 | success: function (data) { 91 | try { 92 | var previewData = parsePreview(data); 93 | var html = "