Git Product home page Git Product logo

oslc-client's Introduction

oslc-client

npm Discourse status Gitter

An OSLC client API Node.js module

oslc-client is a JavaScript Node.js module supporting OSLC client and server development. The client API exposes the OSLC core and domain capabilities through a simple JavaScript API on the OSLC REST services.

oslc-client exploits the dynamic and asynchronous capabilities of JavaScript and Node.js to build and API that can easily adapt to any OSLC domain, extensions to domains, and/or integrations between domains.

This implementation makes use of typical jazz.net application extensions and OSLC usage conventions such as:

Usage

To use oslc-client, include a dependency in your OSLC client app's package.json file:

  "dependencies": {
    "oslc-client": "~1.0.0",
    "async": "^2.6.0"
  }
  • Servers are identified by a server root URL that is typically https://host:port/domain. For example, https://acme.com/ccm would be the server URL for an instance of RTC.
  • Servers provide a rootservices resource at their server root URL that can be used to discover the discovery services provided by the server. This typically provides the URLs to the service provider catalogs and TRS providers. For example https://acme.com/ccm/rootservices provides this information for an instance of RTC. By convention, access to the rootservices resource does not require authentication. This is to provide the OAuth URLs often needed to do authentication.
  • Authentication is done through extensions to request.js that automatically use jazz FORM based authentication by POSTing user credentials to serverURI/j_security_check in response to an authentication challenge indicated by header x-com-ibm-team-repository-web-auth-msg=authrequired
  • Resources are often identified by their dcterms:identifier property, and a readById function is provided to conveniently query resources by ID.

examples

See examples/updateCR.js for an example client application that connects to a server, uses a particular service provider, queries, creates, reads, updates, and deletes ChangeRequest resources managed by RTC.

Contributors

Contributors:

  • Jim Amsden (IBM)

License

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

oslc-client's People

Contributors

berezovskyi avatar jamsden avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oslc-client's Issues

Allow server.read(uri) without a previous server.connect() call to set the server and user credentials

It would be useful to be able to do a server.read(uri) call without having to previously do a server.connect() and server.use(). In this case, the read() operation should be able to throw an unauthorized exception, and let the client do what it wishes to get the user's credentials.

So request.authGet() should not assume a previous connect() call was made and the serverURI and user credentials have not been set.

Rather it should derive the serverURI from the requestURI as needed to do the j_security_check, and if the credentials aren't known, or the authentication challenge fails, then it should throw unauthorized.

Clients running in a browser may often let the browser handle this authentication challenge and post a dialog to get the user's credentials which are then stored by the browser.

Implement OSLCServer.create for a particular resource type

This should do a POST to the service provider creation factory oslc:creation URI with an entity request body that uses a content type acceptable to the host OSLC server (likely RDF/XML).

Ideally it would check the entity request body against the creation factory constraining shape before attempting do to the POST. But we might expect the server to do that anyway, so there might not be a need to do it twice.

Redirection logic in OslcClient class may be faulty

This one took me a while to figure out, and in the end, I managed to pinpoint the cause of the problem in org.eclipse.lyo.client.oslc.OslcClient.getResource(String url, Map<String, String> requestHeaders, String defaultMediaType).

But first things first. Here is the error message that got me started:

SCHWERWIEGEND: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.IllegalStateException: Target host is null
org.apache.wink.client.ClientRuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.IllegalStateException: Target host is null
	at org.apache.wink.client.internal.ResourceImpl.invoke(ResourceImpl.java:241)
	at org.apache.wink.client.internal.ResourceImpl.invoke(ResourceImpl.java:189)
	at org.apache.wink.client.internal.ResourceImpl.invokeNoException(ResourceImpl.java:181)
	at org.apache.wink.client.internal.ResourceImpl.get(ResourceImpl.java:311)
	at org.eclipse.lyo.client.oslc.OslcClient.getResource(OslcClient.java:270)
	at org.eclipse.lyo.client.oslc.OslcOAuthClient.getResource(OslcOAuthClient.java:117)
	at org.eclipse.lyo.client.oslc.OslcClient.getResource(OslcClient.java:204)
	at doors.api.DoorsOauthSampleAdjusted.main(DoorsOauthSampleAdjusted.java:83)
Caused by: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.IllegalStateException: Target host is null
	at org.apache.wink.client.internal.handlers.httpclient.ApacheHttpClientConnectionHandler.handle(ApacheHttpClientConnectionHandler.java:90)
	at org.apache.wink.client.internal.handlers.HandlerContextImpl.doChain(HandlerContextImpl.java:52)
	at org.apache.wink.client.internal.handlers.AcceptHeaderHandler.handle(AcceptHeaderHandler.java:79)
	at org.apache.wink.client.internal.handlers.HandlerContextImpl.doChain(HandlerContextImpl.java:52)
	at org.apache.wink.client.internal.ResourceImpl.invoke(ResourceImpl.java:228)
	... 7 more
Caused by: java.lang.RuntimeException: java.lang.IllegalStateException: Target host is null
	at org.apache.wink.client.internal.handlers.httpclient.ApacheHttpClientConnectionHandler.processRequest(ApacheHttpClientConnectionHandler.java:116)
	at org.apache.wink.client.internal.handlers.httpclient.ApacheHttpClientConnectionHandler.handle(ApacheHttpClientConnectionHandler.java:87)
	... 11 more
Caused by: java.lang.IllegalStateException: Target host is null
	at org.apache.http.util.Asserts.notNull(Asserts.java:52)
	at org.apache.http.impl.conn.DefaultHttpRoutePlanner.determineRoute(DefaultHttpRoutePlanner.java:100)
	at org.apache.http.impl.client.DefaultRequestDirector.determineRoute(DefaultRequestDirector.java:761)
	at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:380)
	at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
	at org.apache.wink.client.internal.handlers.httpclient.ApacheHttpClientConnectionHandler.processRequest(ApacheHttpClientConnectionHandler.java:113)
	... 12 more

After tracking this down for a while, I realized that the root cause of the problem is the while-loop in above mentioned function, which goes like this:

		do {
			[…]

			response = resource.get();

			if (response.getStatusType().getFamily() == Status.Family.REDIRECTION) {
				url = response.getHeaders().getFirst(HttpHeaders.LOCATION);
				response.consumeContent();
				redirect = true;
			} else {
				redirect = false;
			}
		} while (redirect);

Here's what happens when running this, targeting a dwa installation on the local machine:

  • On the first iteration, the url is http://localhost:8080/dwa
  • This begets a 302 redirection response including the header Location=[/dwa/]
  • Now this line url = response.getHeaders().getFirst(HttpHeaders.LOCATION); changes the url from:
    ** From: http://localhost:8080/dwa
    ** To: /dwa/
  • Thus, the host is lost, and the next iteration produces the above mentioned "Target host is null” error

So, to sum it up for this particular case:

  • When a redirection request is received, the url is changed from http://localhost:8080/dwa to /dwa/
  • It should instead be changed from http://localhost:8080/dwa to http://localhost:8080/dwa/

For me, this happened on localhost, and appending the trailing "/" right before the client.getResource call like follows fixed the problem.

ClientResponse response = client.getResource(url + "/", OSLCConstants.CT_RDF);

NOTE: The url in the DoorsOauthSample explicitly needs to have no trailing /, since calls like DWA_URL + "/j_acegi_security_check" rely on it.

However, since this appears to be a problem with the redirection logic, I think this warrants a deeper look.

To reproduce:

The common namespaces defined in ServiceProvider.js should be in a separate file

These common namespaces should be refactored into a separate file so they can be reused by client applications.

var FOAF = rdflib.Namespace("http://xmlns.com/foaf/0.1/");
var RDF = rdflib.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
var RDFS = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#");
var OWL = rdflib.Namespace("http://www.w3.org/2002/07/owl#");
var DC = rdflib.Namespace("http://purl.org/dc/elements/1.1/");
var RSS = rdflib.Namespace("http://purl.org/rss/1.0/");
var XSD = rdflib.Namespace("http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#dt-");
var CONTACT = rdflib.Namespace("http://www.w3.org/2000/10/swap/pim/contact#");
var OSLC = rdflib.Namespace("http://open-services.net/ns/core#");
var OSLCCM = rdflib.Namespace('http://open-services.net/ns/cm#');
var OSLCCM10 = rdflib.Namespace('http://open-services.net/xmlns/cm/1.0/');
var JD = rdflib.Namespace('http://jazz.net/xmlns/prod/jazz/discovery/1.0/');

If the connection is dropped, a subsequent challenge will not be processed

OSLCServer.connect handles the authentication challenge when doing request.get on the service provider catalog. If this authentication connection is dropped by the server for some reason (perhaps due to a timeout), then a subsequent request.get on some OSLC resource will fail because the request.get doesn't automatically handle FORM based authentication.

We could wrap request.get to check for header x-com-ibm-team-repository-web-auth-msg = authrequired, re-establish the authentication, and re-post the request if needed.

But a better approach might be to extend the request module to support FORM based authentication and process the challenge there.

oslc-client uses a dependency with modified AGPL license

StandardError.js is released under a Lesser GNU Affero General Public License. There is no such license, it was made by the author by merging LGPL and AGPL clauses to create a franken-LAGPL. oslc-client shall stop using this library until GNU releases such a license and it becomes OSI/FSF-approved or the author switches to EUPL, for example.

OSLCServer.read take a resourceID, it should probably be a URL

The resource ID is an RTC specific concept - the work item ID, its the GUID. This probably should be exposed in a generic OSLC application. The only identifier is really the URL.

Unfortunately, RTC users aren't that aware of these URLs, but commonly use the work item number. The URL available from the RTC UI is usually not the OSLC URL, but the URL of an RTC view of a work item.

Maybe we need a readBy(resourceID) function as well as a generic read(url) function.

One other thing to consider, the current read(resourceID) is pretty generic since it does do an OSLC query treating the resourceID as the dcterms:identifier. This would be a common practice for OSLC applications. But of course its not required, and servers might not support OSLC Query.

This can be addressed as the need arrises.

function rdflib.fromRDF in ServiceProvider.js is a prototype and should be re-implemented

function rdflib.fromRDF is just a stub implementation to demonstrate the proof of concept for oslc-client. What it does is search the rdflib kb for all properties and values of a subject and constructs a JSON object from that. This works, but is incorrect since it looses all the RDF namespace information on the properties. What that means is that there is no way to reconstruct the RDF from the produced JSON in order to support subsequent PUT of an updated resource.

What this should do is utilize the rdflib or jsonld module to create a JSON object that conforms to a compact JSON-LD object. This retains the RDF namespace information in the @context while still providing consumable JSON for the application.

Class Resource constructor parameters need to include its KB

Each Resource should have a URL, and a KB (rdflib IndexedFormula) that identifies and provides the data for the Resource.

Then Resource should have generic get(property) and set(property) methods that take qualified property names (using rdflib Namespaces) to get and set properties. The implementation does a query on the KB to access the property. This makes Resource completely open and dynamic.

Subclasses of Resource based on OSLC standard domain classes may provide specific methods for properties for convenience, and may choose to cache the properties locally to avoid repeated queries.

OSLC selective properties don't work right

The URI for server.read could include OSLC selective properties. For example:

https://example.com:9443/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/81?oslc.properties=dcterms:title,dcterms:description,dcterms:contributor{foaf:name}&oslc.prefix=dcterms=<http://purl.org/dc/terms/>,oslc_cmx=<http://open-services.net/ns/cm-x%23>,foaf=<http://xmlns.com/foaf/0.1/>

It is not clear from the OSLC Core specification what the URI of the result resource should be. Rational Team Concert returns a result who's URL includes the full query parameter. This seems reasonable because the OSLC selective properties returns a representation of a resource that has a subset of its properties. Having the URI of that representation include the query string lets you know its not the full resource. However, Rational DOORS Next Generation doesn't include the query string in the URL for its response.

In any case, doing a server.read() of the example URL above that includes selective properties does't work properly. server.read() of a resource URI that includes the oslc.selective property query parameter appears to return no properties in the OSLCResource. The reason is that the subject URI encodes the <>'s in the OSLC prefix URIs, but the symbol used for the resource doesn't. So they don't match.

resource.getURI() is:

https://example.com:9443/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/83?oslc.properties=oslc_cm:implementsRequirement&oslc.prefix=oslc_cm=<http://open-services.net/ns/cm%23>,dcterms=<http://purl.org/dc/terms/> 

But the subject index is:

https://example.com:9443/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/83?oslc.properties=oslc_cm:implementsRequirement&oslc.prefix=oslc_cm=%3Chttp://open-services.net/ns/cm%23%3E,dcterms=%3Chttp://purl.org/dc/terms/%3E  

server.query() should return an array of OSLCResources

The server.query function should not return an rdflib IndexedFormula because this would have to be consumed by the API, which should't need to know anything about rdflib.

Instead the query function should return an array of OSLCResources whose URIs correspond to the query result members, and whose kb contains the triples returned from an oslc.select clause if any. It is not expected that these OSLCResources are complete. An additional server.read() would be necessary to get all the properties if needed. Even oslc.select='*' would not necessarily return all the properties.

Review all the error processing

All asynchronous methods that result in errors should return an err code parameter which contains the HTTP status code or appropriate error code. The server should not be logging any errors except internal things that might be needed for debugging.

Implement the OSLCServer.create method

Takes a resource that contains the values of the resource to create.

Eventually each resource should have a reference to its constraining resource shape. This method should check the resource contents against its shape before attempting to POST to the server. This might be redundant with what is already being done by the server, but is a good idea anyway.

This uses the OSLC creation factory service URL in the service provider. The created resource is returned.

The OSLC discovery classes should be implemented as a facade on their RDF KB

Each OSLC discovery class (RootServices, ServiceProviderCatalog, ServicesProvider, Service, CreationFactory, QueryCapability, Dialog, etc.) should be a simple facade on the reflib.js knowledge base internal representation of their RDF resources. These should all be subclasses of OSLCResource. Then accessor methods for the standard OSLC properties should be implemented as simple queries on that KB.

OSLCServer.connect() to deal with oslc_*:*ServiceProviders properties to get the service provider catalogs

Each rootservices document specifies a number of domain service providers using properties such as:

oslc_cm:cmServiceProviders
oslc_rm:rmServiceProviders
oslc_am:amServiceProviders
oslc_qm:qmServiceProviders
etc.

The value of these rootservices properties are the URIs of the server's ServiceProviderCatalogs.

When connecting to a server, we need to read the services provider catalogs in order to initiate the discovery process needed for all other operations.

Currently OSLCServer.connect() is hard coded to use OSLCCM domain

Need to be able to specify what service provider catalog you want to access from rootservices. Or maybe GET all of them into a single KB when establishing the connection?

RootServices.serviceProviderCatalog is is also tied specifically to RTC, finding the domain in the http://jazz.net/xmlns/prod/jazz/discovery/1.0/ namespace.

Implement OSLCServer.update

This should do a PUT of the resource to the resource URI converted an entity request body acceptable to the server (likely RDF/XML for OSLC 2.0 servers). We can assume the server will check the entity request body against the resource shape so there's no need to do it in the client too.

OSLCServer.query uses the wrong kb for the member resources

	var members = kb.each(kb.sym(queryBase), RDFS('member'))
	for (var m in members) {
		var member = new OSLCResource(members[m].uri, kb)
		results.push(member)
	}

The kb used in the OSLCResource constructor is the result of the query, not (necessarily) the result of a GET on the resource. It works for the readById method because the result of that particular query just happens to be a select * on that resource.

For other queries it will be incorrect.

Perhaps OSLCResource should be lazily populated with kb set to undefined. Then an GET could be performed implicitly or explicitly to populate the resource.

On second thought:

  1. OSLCServer.query should return the members as the query result
  2. readById should process the query results for its unique query - it knows the result is a single resource with all properties selected
  3. read should do a direct HTTP GET, and not use a query.

Can't connect

Hi, guys!
I'm new to OSLC.. trying to use this lib for RTC.
I'm following the updateCR.js sample, but I'm having an issue on connect():

I create the server:

var server = new OSLCServer(args.iparams.server_uri, args.iparams.user_id, args.iparams.user_password);
console.log(server)
OSLCServer {
  serverURI: 'https://ommited:9443/ccm',
  userId: 'samuel',
  password: 'samuel',
  rootservices: null,
  serviceProviderCatalog: null,
  serviceProviderTitle: null,
  serviceProvider: null }

When I call connect:
server.connect(OSLCCM10("cmServiceProviders"), callback),

I get:

TypeError: Cannot read property 'uri' of null
    at OSLCServer.read (/home/samuel/workspace/fdk/freshdesk/server/node_modules/oslc-client/server.js:184:48)
    at gotRootServices (/home/samuel/workspace/fdk/freshdesk/server/node_modules/oslc-client/server.js:103:9)
    at gotResult (/home/samuel/workspace/fdk/freshdesk/server/node_modules/oslc-client/server.js:205:3)
    at Request._callback (/home/samuel/workspace/fdk/freshdesk/server/node_modules/oslc-client/oslcRequest.js:74:4)
    at Request.self.callback (/home/samuel/workspace/fdk/freshdesk/server/node_modules/request/request.js:185:22)
    at Request.emit (events.js:198:13)
    at Request.<anonymous> (/home/samuel/workspace/fdk/freshdesk/server/node_modules/request/request.js:1154:10)
    at Request.emit (events.js:198:13)
    at IncomingMessage.<anonymous> (/home/samuel/workspace/fdk/freshdesk/server/node_modules/request/request.js:1076:12)
    at Object.onceWrapper (events.js:286:20)
    at IncomingMessage.emit (events.js:203:15)
    at endReadableNT (_stream_readable.js:1145:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)

What am I missing here?

OSLCServer connect() and use() are jazz.net app specific

OSLCServer currently uses jazz.net specific login on connect() and jazz.net rootservices on use(). These could be made more generic. OSLC core does not mandate an authentication mechanism (but recommends Basic or OAuth/OpenID), nor does OSLC core specify how discovery is discovered. So it is not unusual for these two capabilities to be application specific.

However, some mechanism should be supported that is more open. Perhaps a separate login() function could be added to support Basic authentication. Or connect() could have an additional function argument to use for login.

Instead of rootservices, use() could take a URI to a specific ServiceProviderCatalog and use that directly.

OSLCResource might be the result of a query

The URI for a server.read() call could be to a resource that returned more than one RDF subject. For example, if the URI included an OSLC query parameter. So it could contain more than one member, or be a graph that contains more than one RDF subject. So a server.read() might not actually return an OSLC Resource. How should this be handled?

OSLC 3.0 does introduce LDPCs as potential resources. And OSLC Query Capability can return many resources if there's an oslc.select clause. These are not "OSLC" resources in that that are defined in an OSLC domain specification and are expected to have an identifier, title, description, etc. as defined by OSLC Core. But they are non-the-less OSLC resources.

For now, this implementation assumes that OSLCResource is a resource defined by an OSLC domain vocabulary. a server.read operation on one of these resources would only return one.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.