├── .cfignore
├── .gitignore
├── LICENSE
├── NOTICE
├── Procfile
├── README.md
├── app.js
├── config.json
├── context.json
├── db.js
├── env.js
├── jsonld.js
├── manifest.yml
├── media.js
├── package.json
├── project.json
├── public
├── constraints.html
├── folder.png
├── index.html
└── style.css
├── screenshot.png
├── server.js
├── service.js
├── testng
├── LDPjs-bc.xml
└── LDPjs-dc-simple.xml
├── turtle.js
├── viz.js
└── vocab
├── ldp.js
└── rdf.js
/.cfignore:
--------------------------------------------------------------------------------
1 | launchConfigurations/
2 | .git/
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | launchConfigurations/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | LDP.js Linked Data Platform Reference Implementation
2 | Copyright 2014 IBM Corporation.
3 |
4 | This product includes software developed at IBM (http://www.ibm.com/).
5 |
6 | Portions of this software were adapted from the Eclipse Lyo project
7 | (http://eclipse.org/lyo).
8 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node app.js
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LDP.js
2 |
3 | A simple reference implementation for the [W3C Linked Data
4 | Platform](http://www.w3.org/2012/ldp), leveraging Node.js,
5 | MongoDB, and a few other js libraries. Catch it running at
6 | [http://ldpjs.mybluemix.net](http://ldpjs.mybluemix.net).
7 |
8 | 
9 |
10 | LDP.js supports LDP basic and direct containers. Indirect
11 | containers and non-RDF source are not implemented.
12 |
13 | ## Running
14 |
15 | First, install [Node.js](http://nodejs.org). Next, install and start
16 | [MongoDB](http://docs.mongodb.org/manual/installation/).
17 |
18 | To start the app, run these commands
19 |
20 | $ npm install
21 | $ node app.js
22 |
23 | Finally, point your browser to
24 | [http://localhost:3000/](http://localhost:3000/).
25 |
26 | ## License
27 |
28 | Licensed under the Apache License, Version 2.0 (the "License");
29 | you may not use this file except in compliance with the License.
30 | You may obtain a copy of the License at
31 |
32 | http://www.apache.org/licenses/LICENSE-2.0
33 |
34 | Unless required by applicable law or agreed to in writing, software
35 | distributed under the License is distributed on an "AS IS" BASIS,
36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37 | See the License for the specific language governing permissions and
38 | limitations under the License.
39 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*
18 | * Initializes MongoDB and starts the app. The main application logic is in
19 | * service.js.
20 | */
21 |
22 | var express = require('express');
23 | var env = require('./env.js');
24 |
25 | console.log("configuration:");
26 | console.dir(env);
27 |
28 | // setup middleware
29 | var app = express();
30 | app.use(express.static(__dirname + '/public'));
31 |
32 | // fill in full request URL
33 | app.use(function(req, res, next) {
34 | req.fullURL = env.appBase + req.originalUrl;
35 | next();
36 | });
37 |
38 | // fill in req.rawBody
39 | app.use(function(req, res, next) {
40 | req.rawBody = '';
41 | req.setEncoding('utf8');
42 |
43 | req.on('data', function(chunk) {
44 | req.rawBody += chunk;
45 | });
46 |
47 | req.on('end', function() {
48 | next();
49 | });
50 | });
51 |
52 | // initialize database and set up LDP services and viz when ready
53 | var db = require('./db.js');
54 | db.init(env, function(err) {
55 | if (err) {
56 | console.error(err);
57 | console.error("Can't initialize MongoDB.");
58 | } else {
59 | require('./service.js')(app, db, env);
60 | require('./viz.js')(app, db, env);
61 | }
62 | });
63 |
64 | // error handling
65 | app.use(function(err, req, res, next){
66 | console.error(err.stack);
67 | res.send(500, 'Something broke!');
68 | });
69 |
70 | // Start server
71 | app.listen(env.listenPort, env.listenHost);
72 | console.log('App started on port ' + env.listenPort);
73 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "scheme": "http",
3 | "host": "localhost",
4 | "port": 3000,
5 | "context": "/r",
6 | "mongoURL": "mongodb://localhost:27017/ldp"
7 | }
8 |
--------------------------------------------------------------------------------
/context.json:
--------------------------------------------------------------------------------
1 | {
2 | "ldp": "http://www.w3.org/ns/ldp#",
3 | "dcterms": "http://purl.org/dc/terms/",
4 | "foaf": "http://xmlns.com/foaf/0.1/"
5 | }
6 |
--------------------------------------------------------------------------------
/db.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | /*
19 | * db.js stores RDF graphs in MongoDB. Each document representations
20 | * an RDF graph. Documents have the properties below. The 'triples'
21 | * property is the RDF. Other properties are metadata.
22 | *
23 | * All documents:
24 | *
25 | * name - the URI of the graph
26 | * interactionModel - the URI indicating the LDP interaction model of the resource
27 | * container - the container for this resource
28 | * deleted - boolean indicating if the resource has been deleted (to avoid reusing URIs)
29 | * triples - an array of RDF triples in N3.js format
30 | *
31 | * Direct containers:
32 | *
33 | * membershipResource - the ldp:membershipResource property
34 | * hasMemberRelation - the ldp:hasMemberRelation property
35 | * isMemberOfRelation - the ldp:isMemberOfRelation property
36 | *
37 | * Membership resources:
38 | *
39 | * membershipResourceFor - the associated direct container (always 1:1)
40 | *
41 | * Rather than storing a link to all of its members in the container,
42 | * we have a property in each resource that points back to its
43 | * container. On a container GET, we query for a container's resources
44 | * and mix in containment triples.
45 | */
46 |
47 | var ldp = require('./vocab/ldp.js'); // LDP vocabulary
48 |
49 | var db;
50 |
51 | function graphs() {
52 | return db.collection('graphs');
53 | }
54 |
55 | // index the graph name for fast lookups and uniqueness
56 | function ensureIndex() {
57 | graphs().ensureIndex({
58 | name: 1
59 | }, {
60 | unique: true
61 | }, function(err) {
62 | if (err) {
63 | // not fatal, but log the error
64 | console.log(err.stack);
65 | }
66 | });
67 | }
68 |
69 | exports.init = function(env, callback) {
70 | require('mongodb').connect(env.mongoURL, function(err, conn) {
71 | if (err) {
72 | callback(err);
73 | return;
74 | }
75 |
76 | db = conn;
77 | exports.graphs = graphs();
78 | console.log("Connected to MongoDB at: "+env.mongoURL);
79 | ensureIndex();
80 | callback();
81 | });
82 | };
83 |
84 | exports.drop = function(callback) {
85 | graphs().drop(callback);
86 | ensureIndex();
87 | exports.graphs = graphs();
88 | };
89 |
90 | exports.reserveURI = function(uri, callback) {
91 | // simply create a document with only a URI. we will just update it later on put
92 | // if it fails, we reject the uri
93 | graphs().insert({
94 | name: uri
95 | }, function(err, result) {
96 | callback(err);
97 | });
98 | };
99 |
100 | exports.releaseURI = function(uri) {
101 | graphs().remove({
102 | name: uri
103 | }, function(err) {
104 | if (err) {
105 | console.log(err.stack);
106 | }
107 | });
108 | };
109 |
110 | exports.put = function(doc, callback) {
111 | console.log('db.put');
112 | console.dir(doc);
113 | graphs().update({
114 | name: doc.name
115 | }, doc, {
116 | upsert: true,
117 | safe: true
118 | }, callback);
119 | };
120 |
121 | exports.get = function(uri, callback) {
122 | console.log('db.get');
123 | graphs().find({
124 | name: uri
125 | }, {
126 | limit: 1
127 | }).toArray(function(err, docs) {
128 | if (docs && docs[0]) {
129 | console.dir(docs[0]);
130 | callback(err, docs[0]);
131 | } else {
132 | callback(err);
133 | }
134 | });
135 | };
136 |
137 | exports.remove = function(uri, callback) {
138 | graphs().update({
139 | name: uri,
140 | }, {
141 | $set: {
142 | deleted: true,
143 | containedBy: null,
144 | triples: []
145 | }
146 | }, {
147 | safe: true
148 | }, callback);
149 | };
150 |
151 | exports.findContainer = function(uri, callback) {
152 | graphs().find({
153 | name: uri,
154 | $or: [{
155 | interactionModel: ldp.BasicContainer
156 | }, {
157 | interactionModel: ldp.DirectContainer
158 | }],
159 | deleted: {
160 | $ne: true
161 | }
162 | }, {
163 | name: 1,
164 | interactionModel: 1,
165 | membershipResource: 1,
166 | hasMemberRelation: 1,
167 | isMemberOfRelation: 1
168 | }, {
169 | limit: 1
170 | }).toArray(function(err, docs) {
171 | callback(err, (docs && docs.length) ? docs[0] : null);
172 | });
173 | };
174 |
175 | exports.getContainment = function(uri, callback) {
176 | graphs().find({
177 | containedBy: uri,
178 | deleted: {
179 | $ne: true
180 | }
181 | }, {
182 | name: 1
183 | }).toArray(function(err, docs) {
184 | var result = [];
185 | if (docs) {
186 | docs.forEach(function(doc) {
187 | result.push(doc.name);
188 | });
189 | }
190 |
191 | callback(err, result);
192 | });
193 | };
194 |
195 | exports.createMembershipResource = function(document, callback) {
196 | graphs().update({
197 | name: document.membershipResource
198 | }, {
199 | $push: {
200 | membershipResourceFor: {
201 | container: document.name,
202 | hasMemberRelation: document.hasMemberRelation
203 | }
204 | }
205 | }, {
206 | upsert: true,
207 | safe: true
208 | }, callback);
209 | };
210 |
--------------------------------------------------------------------------------
/env.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*
18 | * Looks at environment variables for app configuration (base URI, port, LDP
19 | * context, etc.), falling back to what's in confg.json.
20 | */
21 |
22 | var url = require("url");
23 | var config = require('./config.json');
24 |
25 | // the IP address of the Cloud Foundry DEA (Droplet Execution Agent) that hosts this application:
26 | exports.listenHost = (process.env.VCAP_APP_HOST || process.env.OPENSHIFT_NODEJS_IP || config.host);
27 |
28 | // the port on the DEA for communication with the application:
29 | exports.listenPort = (process.env.VCAP_APP_PORT || process.env.OPENSHIFT_NODEJS_PORT || config.port);
30 |
31 | function addSlash(url) {
32 | if (url.substr(-1) == '/') {
33 | return url;
34 | } else {
35 | return url + '/';
36 | }
37 | }
38 |
39 | function toURL(urlObj) {
40 | if ((urlObj.scheme === 'http' && urlObj.port === 80) ||
41 | (urlObj.scheme === 'https' && urlObj.port === 443)) {
42 | delete urlObj.port;
43 | }
44 |
45 | return url.format(urlObj);
46 | }
47 |
48 | // scheme, host, port, and base URI
49 | var appInfo = JSON.parse(process.env.VCAP_APPLICATION || "{}");
50 | if (process.env.LDP_BASE) {
51 | // LDP_BASE env var set
52 | exports.ldpBase = addSlash(process.env.LDP_BASE);
53 | var url = url.parse(exports.ldpBase);
54 | exports.scheme = url.scheme;
55 | exports.host = url.host;
56 | exports.port = url.port;
57 | exports.context = url.pathname;
58 | exports.appBase = toURL({
59 | protocol: exports.scheme,
60 | host: exports.host,
61 | port: exports.port
62 | });
63 | } else {
64 | // no LDP_BASE set
65 | exports.scheme = (process.env.VCAP_APP_PORT) ? 'http' :config.scheme;
66 | if (appInfo.application_uris) {
67 | exports.host = appInfo.application_uris[0];
68 | } else {
69 | exports.host = process.env.HOSTNAME || config.host;
70 | }
71 |
72 | // public port is the default in a Bluemix environment
73 | if (!process.env.VCAP_APP_PORT) {
74 | exports.port = config.port;
75 | }
76 | exports.context = addSlash(config.context);
77 |
78 | exports.appBase = toURL({
79 | protocol: exports.scheme,
80 | hostname: exports.host,
81 | port: exports.port
82 | });
83 |
84 | exports.ldpBase = toURL({
85 | protocol: exports.scheme,
86 | hostname: exports.host,
87 | port: exports.port,
88 | pathname: exports.context
89 | });
90 | }
91 |
92 | // MongoDB
93 | if (process.env.VCAP_SERVICES) {
94 | var env = JSON.parse(process.env.VCAP_SERVICES);
95 | exports.mongoURL = env.mongolab[0].credentials.uri;
96 | } else {
97 | if (process.env.OPENSHIFT_MONGODB_DB_URL) {
98 | exports.mongoURL = process.env.OPENSHIFT_MONGODB_DB_URL;
99 | } else {
100 | exports.mongoURL = process.env.MONGO_URL || config.mongoURL;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/jsonld.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*
18 | * Parses and serializes JSON-LD (application/ld+json) to and from an array of
19 | * triples, the triple format used by N3.js.
20 | */
21 |
22 | var N3 = require('n3');
23 | var jsonld = require('jsonld');
24 | var media = require('./media.js'); // media types
25 | var ldp = require('./vocab/ldp.js'); // LDP vocabulary
26 | var rdf = require('./vocab/rdf.js'); // RDF vocabulary
27 | var context = require('./context.json'); // our JSON-LD context
28 |
29 | exports.parse = function (req, resourceURI, callback) {
30 | var json = JSON.parse(req.rawBody);
31 | jsonld.toRDF(json, { base: resourceURI }, function(err, dataset) {
32 | if (err) {
33 | callback(err);
34 | return;
35 | }
36 |
37 | // transform the dataset to the N3.js triples format we use in our database
38 | // both libraries use a different internal format unfortunately
39 | var result = [];
40 | for(var graphName in dataset) {
41 | var triples = dataset[graphName];
42 | // FIXME: what about graph names?
43 | triples.forEach(function (triple) {
44 | var next = {};
45 | next.subject = triple.subject.value;
46 | next.predicate = triple.predicate.value;
47 | if (triple.object.type === 'IRI' || triple.object.type === 'blank node') {
48 | next.object = triple.object.value;
49 | } else {
50 | var literal = '"' + triple.object.value + '"';
51 | if (triple.object.language) {
52 | literal += '@' + triple.object.language;
53 | } else if (triple.object.datatype && triple.object.datatype !== 'http://www.w3.org/2001/XMLSchema#string') {
54 | literal += '^^<' + triple.object.datatype + '>';
55 | }
56 | next.object = literal;
57 | }
58 |
59 | result.push(next);
60 | });
61 | }
62 |
63 | callback(null, result);
64 | });
65 | };
66 |
67 | function jsonldResource(subject) {
68 | return { '@id': subject };
69 | }
70 |
71 | function jsonldObject(object) {
72 | if (N3.Util.isUri(object) || N3.Util.isBlank(object)) {
73 | return jsonldResource(object);
74 | }
75 |
76 | var result = {};
77 | var value = N3.Util.getLiteralValue(object);
78 | result['@value'] = value;
79 | var type = N3.Util.getLiteralType(object);
80 | if (type && type !== 'http://www.w3.org/2001/XMLSchema#string') {
81 | result['@type'] = type;
82 | }
83 | var language = N3.Util.getLiteralLanguage(object);
84 | if (language) {
85 | result['@language'] = language;
86 | }
87 |
88 | return result;
89 | }
90 |
91 | exports.serialize = function(triples, callback) {
92 | var resources = [];
93 | var map = {};
94 |
95 | triples.forEach(function(triple) {
96 | var sub = map[triple.subject];
97 | if (!sub) {
98 | sub = jsonldResource(triple.subject);
99 | map[triple.subject] = sub;
100 | resources.push(sub);
101 | }
102 |
103 | var object;
104 | if ((N3.Util.isUri(triple.object) || N3.Util.isBlank(triple.object)) && !map[triple.object]) {
105 | object = jsonldResource(triple.object);
106 | }
107 |
108 | if (triple.predicate === rdf.type) {
109 | jsonld.addValue(sub, '@type', triple.object, { propertyIsArray: true });
110 | return;
111 | }
112 |
113 | object = jsonldObject(triple.object);
114 | jsonld.addValue(sub, triple.predicate, object, { propertyIsArray: true });
115 | });
116 |
117 | jsonld.compact(resources, context, function(err, json) {
118 | if (err) {
119 | callback(err);
120 | }
121 |
122 | var content = JSON.stringify(json, undefined, 4);
123 | callback(null, media.jsonld, content);
124 | });
125 | };
126 |
--------------------------------------------------------------------------------
/manifest.yml:
--------------------------------------------------------------------------------
1 | applications:
2 | - disk_quota: 1024M
3 | host: ldpjs
4 | name: LDP.js
5 | command: node app.js
6 | path: .
7 | domain: mybluemix.net
8 | instances: 1
9 | memory: 128M
10 |
--------------------------------------------------------------------------------
/media.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*
18 | * Media type constants.
19 | */
20 |
21 | function define(name, value) {
22 | Object.defineProperty(exports, name, {
23 | value: value,
24 | enumerable: true
25 | });
26 | }
27 |
28 | define('turtle', 'text/turtle');
29 | define('text', 'text/plain');
30 | define('n3', 'text/n3');
31 | define('jsonld', 'application/ld+json');
32 | define('json', 'application/json');
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LDP.js",
3 | "version": "0.0.1",
4 | "description": "A Node.js reference app for LDP",
5 | "dependencies": {
6 | "express": "4.14.0",
7 | "n3": "0.2.7",
8 | "jsonld": "0.4.11",
9 | "mongodb": "2.2.5"
10 | },
11 | "engines": {
12 | "node": "0.10.26"
13 |
14 | },
15 | "scripts": {
16 | "start": "node app.js"
17 | },
18 | "repository": {}
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/project.json:
--------------------------------------------------------------------------------
1 | {"Name":"LDPjs"}
2 |
--------------------------------------------------------------------------------
/public/constraints.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
22 |
23 | LDP.js
24 |
25 |
26 |
27 |
28 |
LDP.js
29 |
30 |
LDP.js is an LDP server built using Node.js and MongoDB. It accepts most any RDF,
33 | but does have a few constraints:
34 |
35 |
36 |
LDP.js does not yet support non-RDF source.
37 |
LDP.js does not support ldp:IndirectContainer.
38 |
Containers cannot be modified via PUT.
39 |
The interaction model of a resource cannot be changed by modifying
40 | its rdf:type after creation. If the resource was created as RDF Source,
41 | it cannot be changed to a container.
42 |
Deleting a container does not delete any of its resources.
43 |
44 |
45 |
DISCLAIMER: This service is provided as-is. It could go
46 | down at any time and change without warning. In fact, it might have
47 | already changed while you were reading this. The data behind this
48 | services will get scrubbed periodically. If this service gets abused,
49 | wse will either take it down or restrict access. Play nice.
DISCLAIMER: This service is provided as-is. It could go
109 | down at any time and change without warning. In fact, it might have
110 | already changed while you were reading this. The data behind this
111 | services will get scrubbed periodically. If this service gets abused,
112 | we will either take it down or restrict access. Play nice.
113 |
114 |
115 |
116 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
--------------------------------------------------------------------------------
/public/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | html {
18 | color: #000;
19 | background: #FFF;
20 | }
21 |
22 | body {
23 | font-family: "Helvetica Neue",Helvetica,Arial,Verdana,Geneva,sans-serif;
24 | color: #333;
25 | }
26 |
27 | .tabs {
28 | margin-bottom: 0.5em;
29 | }
30 |
31 | .fixed {
32 | font-family:monospace;
33 | padding: 1em;
34 | border: 1px dashed #2f6fab;
35 | color: black;
36 | background-color: #f9f9f9;
37 | line-height: 1.1em;
38 | white-space: pre;
39 | overflow-x: scroll;
40 | margin-top: 0.5em;
41 | }
42 |
43 | .urlInput {
44 | width: 40em;
45 | }
46 |
47 | .message {
48 | margin-bottom: 0.5em;
49 | }
50 |
51 | .textArea {
52 | display: block;
53 | width: 100%;
54 | height: 20em;
55 | -webkit-box-sizing: border-box;
56 | -moz-box-sizing: border-box;
57 | box-sizing: border-box;
58 | margin-bottom: 0.5em;
59 | }
60 |
61 | .error {
62 | color: red;
63 | }
64 |
65 | circle {
66 | stroke: #fff;
67 | stroke-width: 1.5px;
68 | }
69 |
70 | .link {
71 | stroke: #999;
72 | stroke-opacity: .6;
73 | }
74 |
75 | text {
76 | fill: #000;
77 | font: 10px sans-serif;
78 | pointer-events: none;
79 | }
80 |
81 | label {
82 | font-size: 80%;
83 | }
84 |
85 | input[type=checkbox] {
86 | vertical-align: middle;
87 | position: relative;
88 | bottom: 1px;
89 | }
90 |
91 | blockquote {
92 | border: 1px solid #E0CB52;
93 | background-color: #FCFAEE;
94 | padding: 5px;
95 | }
96 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spadgett/LDPjs/9ab1dba9ea42ab1a187a2c3718f9a618fa50ade7/screenshot.png
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | require("./app.js");
2 |
--------------------------------------------------------------------------------
/service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*
18 | * service.js handles HTTP requests for LDP resources.
19 | */
20 |
21 | module.exports = function(app, db, env) {
22 | var ldp = require('./vocab/ldp.js'); // LDP vocabulary
23 | var rdf = require('./vocab/rdf.js'); // RDF vocabulary
24 | var media = require('./media.js'); // media types
25 | var turtle = require('./turtle.js'); // text/turtle parsing and serialization
26 | var jsonld = require('./jsonld.js'); // application/ld+json parsing and serialization
27 | var crypto = require('crypto'); // for MD5 (ETags)
28 |
29 | // create root container if it doesn't exist
30 | db.get(env.ldpBase, function(err, document) {
31 | if (err) {
32 | console.log(err.stack);
33 | return;
34 | }
35 |
36 | if (!document || document.deleted) {
37 | createRootContainer(function(err) {
38 | if (err) {
39 | console.log(err.stack);
40 | }
41 | });
42 | }
43 | });
44 |
45 | // route any requests matching the LDP context (defaults to /r/*)
46 | var resource = app.route(env.context + '*');
47 | resource.all(function(req, res, next) {
48 | // all responses should have Link: rel=type
49 | var links = {
50 | type: ldp.Resource
51 | };
52 | // also include implementation constraints
53 | links[ldp.constrainedBy] = env.appBase + '/constraints.html';
54 | res.links(links);
55 | next();
56 | });
57 |
58 | function get(req, res, includeBody) {
59 | res.set('Vary', 'Accept');
60 | db.get(req.fullURL, function(err, document) {
61 | if (err) {
62 | console.log(err.stack);
63 | res.sendStatus(500);
64 | return;
65 | }
66 |
67 | if (!document) {
68 | res.sendStatus(404);
69 | return;
70 | }
71 |
72 | if (document.deleted) {
73 | res.sendStatus(410);
74 | return;
75 | }
76 |
77 | // determine what format to serialize using the Accept header
78 | var serialize;
79 | if (req.accepts(media.turtle)) {
80 | serialize = turtle.serialize;
81 | } else if (req.accepts(media.jsonld) || req.accepts(media.json)) {
82 | serialize = jsonld.serialize;
83 | } else {
84 | res.sendStatus(406);
85 | return;
86 | }
87 |
88 | // add common response headers
89 | addHeaders(res, document);
90 |
91 | // some triples like containment are calculated on-the-fly rather
92 | // than being stored in the document
93 | // insertCalculatedTriples also looks at the Prefer header to see
94 | // what to include
95 | insertCalculatedTriples(req, document, function(err, preferenceApplied) {
96 | if (err) {
97 | console.log(err.stack);
98 | res.sendStatus(500);
99 | return;
100 | }
101 |
102 | serialize(document.triples, function(err, contentType, content) {
103 | if (err) {
104 | console.log(err.stack);
105 | res.sendStatus(500);
106 | return;
107 | }
108 |
109 | if (preferenceApplied) {
110 | res.set('Preference-Applied', 'return=representation');
111 | }
112 |
113 | // generate an ETag for the content
114 | var eTag = getETag(content);
115 | if (req.get('If-None-Match') === eTag) {
116 | res.sendStatus(304);
117 | return;
118 | }
119 |
120 | res.writeHead(200, {
121 | 'ETag': eTag,
122 | 'Content-Type': contentType
123 | });
124 | if (includeBody) {
125 | res.end(new Buffer(content), 'utf-8');
126 | } else {
127 | res.end();
128 | }
129 | });
130 | });
131 | });
132 | }
133 |
134 | resource.get(function(req, res, next) {
135 | console.log('GET ' + req.path);
136 | get(req, res, true);
137 | });
138 |
139 | resource.head(function(req, res, next) {
140 | console.log('HEAD ' + req.path);
141 | get(req, res, false);
142 | });
143 |
144 | // allow dropping the database using DELETE /db
145 | // not recommended for production servers ;)
146 | app.delete('/db', function(req, res, next) {
147 | db.drop(function(err) {
148 | if (err) {
149 | console.log(err.stack);
150 | res.sendStatus(500);
151 | } else {
152 | createRootContainer(function(err) {
153 | if (err) {
154 | console.log(err.stack);
155 | res.sendStatus(500);
156 | }
157 |
158 | res.sendStatus(204);
159 | });
160 | }
161 | });
162 | });
163 |
164 | function putUpdate(req, res, document, newTriples, serialize) {
165 | if (isContainer(document)) {
166 | res.set('Allow', 'GET,HEAD,DELETE,OPTIONS,POST').sendStatus(405);
167 | return;
168 | }
169 |
170 | var ifMatch = req.get('If-Match');
171 | if (!ifMatch) {
172 | res.sendStatus(428);
173 | return;
174 | }
175 |
176 | // add membership triples if necessary to calculate the correct ETag
177 | insertCalculatedTriples(null, document, function(err) {
178 | if (err) {
179 | console.log(err.stack);
180 | res.sendStatus(500);
181 | return;
182 | }
183 |
184 | if (req.is(media.turtle)) {
185 | serialize = turtle.serialize;
186 | } else {
187 | serialize = jsonld.serialize;
188 | }
189 |
190 | // calculate the ETag from the matching representation
191 | serialize(document.triples, function(err, contentType, content) {
192 | if (err) {
193 | console.log(err.stack);
194 | res.sendStatus(500);
195 | return;
196 | }
197 |
198 | var eTag = getETag(content);
199 | if (ifMatch !== eTag) {
200 | res.sendStatus(412);
201 | return;
202 | }
203 |
204 | // remove any containment triples from the request body if this
205 | // is a container. then update the document with the new
206 | // triples. we store containment with the resources
207 | // themselves, not in the container document.
208 | document.triples = newTriples;
209 |
210 | // determine if there are changes to the interaction model
211 | updateInteractionModel(document);
212 |
213 | // remove any membership triples if this is a membership
214 | // resource so we don't store them directly
215 | removeMembership(document);
216 |
217 | db.put(document, function(err) {
218 | if (err) {
219 | console.log(err.stack);
220 | res.sendStatus(500);
221 | return;
222 | }
223 |
224 | res.sendStatus(204);
225 | });
226 | });
227 | });
228 | }
229 |
230 | function putCreate(req, res, triples) {
231 | var document = {
232 | name: req.fullURL,
233 | triples: triples
234 | };
235 | updateInteractionModel(document);
236 |
237 | // check if the client requested a specific interaction model through a
238 | // Link header. if so, override what we found from the RDF content.
239 | // FIXME: look for Link type=container as well
240 | if (hasResourceLink(req)) {
241 | document.interactionModel = ldp.RDFSource;
242 | }
243 |
244 | // check the membership triple pattern if this is a direct container
245 | if (!isMembershipPatternValid(document)) {
246 | res.sendStatus(409);
247 | return;
248 | }
249 |
250 | db.put(document, function(err) {
251 | if (err) {
252 | console.log(err.stack);
253 | res.sendStatus(500);
254 | return;
255 | }
256 |
257 | // create a membership resource if necessary.
258 | createMembershipResource(document, function(err) {
259 | if (err) {
260 | console.log(err.stack);
261 | db.releaseURI(loc);
262 | res.sendStatus(500);
263 | return;
264 | }
265 |
266 | res.sendStatus(201);
267 | });
268 | });
269 | }
270 |
271 | resource.put(function(req, res, next) {
272 | console.log('PUT ' + req.path);
273 | var parse, serialize;
274 | if (req.is(media.turtle)) {
275 | parse = turtle.parse;
276 | serialize = turtle.serialize;
277 | } else if (req.is(media.jsonld) || req.is(media.json)) {
278 | parse = jsonld.parse;
279 | serialize = jsonld.serialize;
280 | } else {
281 | res.sendStatus(415);
282 | return;
283 | }
284 |
285 | parse(req, req.fullURL, function(err, newTriples) {
286 | if (err) {
287 | res.sendStatus(400);
288 | return;
289 | }
290 |
291 | // get the resource to check if it exists and check its ETag
292 | db.get(req.fullURL, function(err, document) {
293 | if (err) {
294 | console.log(err.stack);
295 | res.sendStatus(500);
296 | }
297 |
298 | if (document) {
299 | if (document.deleted) {
300 | res.sendStatus(410);
301 | return;
302 | }
303 |
304 | // the resource exists. update it
305 | putUpdate(req, res, document, newTriples, serialize);
306 | } else {
307 | putCreate(req, res, newTriples);
308 | }
309 | });
310 | });
311 | });
312 |
313 | resource.post(function(req, res, next) {
314 | console.log('POST ' + req.path);
315 | db.findContainer(req.fullURL, function(err, container) {
316 | if (err) {
317 | console.log(err.stack);
318 | res.sendStatus(500);
319 | return;
320 | }
321 |
322 | if (!container) {
323 | res.set('Allow', 'GET,HEAD,PUT,DELETE,OPTIONS').sendStatus(405);
324 | return;
325 | }
326 |
327 | var parse;
328 | if (req.is(media.turtle)) {
329 | parse = turtle.parse;
330 | } else if (req.is(media.jsonld) || req.is(media.json)) {
331 | parse = jsonld.parse;
332 | } else {
333 | res.sendStatus(415);
334 | return;
335 | }
336 |
337 | assignURI(req.fullURL, req.get('Slug'), function(err, loc) {
338 | if (err) {
339 | console.log(err.stack);
340 | res.sendStatus(500);
341 | return;
342 | }
343 |
344 | parse(req, loc, function(err, triples) {
345 | if (err) {
346 | // allow the URI to be used again
347 | db.releaseURI(loc);
348 | res.sendStatus(400);
349 | return;
350 | }
351 |
352 | var document = {
353 | name: loc,
354 | containedBy: req.fullURL,
355 | triples: triples
356 | };
357 |
358 | updateInteractionModel(document);
359 | addHeaders(res, document);
360 |
361 | // check if the client requested a specific interaction model through a Link header
362 | // if so, override what we found from the RDF content
363 | // FIXME: look for Link type=container as well
364 | if (hasResourceLink(req)) {
365 | document.interactionModel = ldp.RDFSource;
366 | }
367 |
368 | // check the membership triple pattern if this is a direct container
369 | if (!isMembershipPatternValid(document)) {
370 | db.releaseURI(loc);
371 | res.sendStatus(409);
372 | return;
373 | }
374 |
375 | // add the "inverse" isMemberOfRelation link if needed
376 | if (container.interactionModel === ldp.DirectContainer &&
377 | container.isMemberOfRelation) {
378 | document.triples.push({
379 | subject: loc,
380 | predicate: container.isMemberOfRelation,
381 | object: req.fullURL
382 | });
383 | }
384 |
385 | // create the resource
386 | db.put(document, function(err) {
387 | if (err) {
388 | console.log(err.stack);
389 | db.releaseURI(loc);
390 | res.sendStatus(500);
391 | return;
392 | }
393 |
394 | // create a membership resource if necessary.
395 | createMembershipResource(document, function(err) {
396 | if (err) {
397 | console.log(err.stack);
398 | db.releaseURI(loc);
399 | res.sendStatus(500);
400 | return;
401 | }
402 |
403 | res.location(loc).sendStatus(201);
404 | });
405 | });
406 | });
407 | });
408 | });
409 | });
410 |
411 | resource.delete(function(req, res, next) {
412 | console.log('DELETE: ' + req.path);
413 | db.remove(req.fullURL, function(err, result) {
414 | if (err) {
415 | console.log(err.stack);
416 | res.sendStatus(500);
417 | return;
418 | }
419 |
420 | res.sendStatus(result ? 204 : 404);
421 | });
422 | });
423 |
424 | resource.options(function(req, res, next) {
425 | db.get(req.fullURL, function(err, document) {
426 | if (err) {
427 | console.log(err.stack);
428 | res.sendStatus(500);
429 | return;
430 | }
431 |
432 | if (!document) {
433 | res.sendStatus(404);
434 | return;
435 | }
436 |
437 | if (document.deleted) {
438 | res.sendStatus(410);
439 | return;
440 | }
441 |
442 | addHeaders(res, document);
443 | res.sendStatus(200);
444 | });
445 | });
446 |
447 | // creates a root container on first run
448 | function createRootContainer(callback) {
449 | var triples = [{
450 | subject: env.ldpBase,
451 | predicate: rdf.type,
452 | object: ldp.Resource
453 | }, {
454 | subject: env.ldpBase,
455 | predicate: rdf.type,
456 | object: ldp.RDFSource
457 | }, {
458 | subject: env.ldpBase,
459 | predicate: rdf.type,
460 | object: ldp.Container
461 | }, {
462 | subject: env.ldpBase,
463 | predicate: rdf.type,
464 | object: ldp.BasicContainer
465 | }, {
466 | subject: env.ldpBase,
467 | predicate: 'http://purl.org/dc/terms/title',
468 | object: '"LDP.js root container"'
469 | }];
470 |
471 | db.put({
472 | name: env.ldpBase,
473 | interactionModel: ldp.BasicContainer,
474 | triples: triples,
475 | deleted: false
476 | }, callback);
477 | }
478 |
479 | // create a membership resource for the container if it's a direct
480 | // container and the membership resource is not the container itself
481 | function createMembershipResource(document, callback) {
482 | if (document.interactionModel === ldp.DirectContainer &&
483 | document.membershipResource &&
484 | document.membershipResource !== document.name) {
485 | // create membership resource
486 | db.createMembershipResource(document, callback);
487 | } else {
488 | callback();
489 | }
490 | }
491 |
492 | // generate an ETag for a response using an MD5 hash
493 | // note: insert any calculated triples before calling getETag()
494 | function getETag(content) {
495 | return 'W/"' + crypto.createHash('md5').update(content).digest('hex') + '"';
496 | }
497 |
498 | // add common headers to all responses
499 | function addHeaders(res, document) {
500 | var allow = 'GET,HEAD,DELETE,OPTIONS';
501 | if (isContainer(document)) {
502 | res.links({
503 | type: document.interactionModel
504 | });
505 | allow += ',POST';
506 | res.set('Accept-Post', media.turtle + ',' + media.jsonld + ',' + media.json);
507 | } else {
508 | allow += ',PUT';
509 | }
510 |
511 | res.set('Allow', allow);
512 | }
513 |
514 | // checks if document represents a basic or direct container
515 | // this is set using document.interactionModel and can't be changed
516 | // we don't look at the RDF type
517 | function isContainer(document) {
518 | return document.interactionModel === ldp.BasicContainer || document.interactionModel === ldp.DirectContainer;
519 | }
520 |
521 | // look at the triples to determine the type of container if this is a
522 | // container and, if a direct container, its membership pattern
523 | function updateInteractionModel(document) {
524 | var interactionModel = ldp.RDFSource;
525 | document.triples.forEach(function(triple) {
526 | var s = triple.subject,
527 | p = triple.predicate,
528 | o = triple.object;
529 | if (s !== document.name) {
530 | return;
531 | }
532 |
533 | // determine the interaction model from the RDF type
534 | // direct takes precedence if the resource has both direct and basic RDF types
535 | if (p === rdf.type && interactionModel !== ldp.DirectContainer && (o === ldp.BasicContainer || o === ldp.DirectContainer)) {
536 | interactionModel = o;
537 | return;
538 | }
539 |
540 | if (p === ldp.membershipResource) {
541 | document.membershipResource = o;
542 | return;
543 | }
544 |
545 | if (p === ldp.hasMemberRelation) {
546 | document.hasMemberRelation = o;
547 | }
548 |
549 | if (p === ldp.isMemberOfRelation) {
550 | document.isMemberOfRelation = o;
551 | }
552 | });
553 |
554 | // don't override an existing interaction model
555 | if (!document.interactionModel) {
556 | document.interactionModel = interactionModel;
557 | }
558 | }
559 |
560 | // determine if this is a membership resource. if it is, insert the
561 | // membership triples.
562 | function insertMembership(req, document, callback) {
563 | var patterns = document.membershipResourceFor;
564 | if (patterns) {
565 | if (hasPreferOmit(req, ldp.PreferMembership)) {
566 | callback(null, true); // preference applied
567 | return;
568 | }
569 |
570 | // respond with Preference-Applied: return=representation if
571 | // membership was explicitly requested
572 | var preferenceApplied = hasPreferInclude(req, ldp.PreferMembership);
573 | var inserted = 0;
574 | patterns.forEach(function(pattern) {
575 | db.getContainment(pattern.container, function(err, containment) {
576 | if (err) {
577 | callback(err);
578 | return;
579 | }
580 |
581 | if (containment) {
582 | containment.forEach(function(resource) {
583 | document.triples.push({
584 | subject: document.name,
585 | predicate: pattern.hasMemberRelation,
586 | object: resource
587 | });
588 | });
589 | }
590 |
591 | if (++inserted === patterns.length) {
592 | callback(null, preferenceApplied);
593 | }
594 | });
595 | });
596 | } else {
597 | callback(null, false);
598 | }
599 | }
600 |
601 | // insert any dynamically calculated triples
602 | function insertCalculatedTriples(req, document, callback) {
603 | // insert membership if this is a membership resource
604 | insertMembership(req, document, function(err, preferenceApplied) {
605 | if (err) {
606 | callback(err);
607 | return;
608 | }
609 |
610 | // next insert any dynamic triples if this is a container
611 | if (!isContainer(document)) {
612 | callback(null, preferenceApplied);
613 | return;
614 | }
615 |
616 | // check if client is asking for a minimal container
617 | var minimal = false;
618 | if (hasPreferInclude(req, ldp.PreferMinimalContainer) ||
619 | hasPreferInclude(req, ldp.PreferEmptyContainer)) {
620 | preferenceApplied = true;
621 | minimal = true;
622 | }
623 |
624 | // include containment?
625 | var includeContainment;
626 | if (hasPreferInclude(req, ldp.PreferContainment)) {
627 | includeContainment = true;
628 | preferenceApplied = true;
629 | } else if (hasPreferOmit(req, ldp.PreferContainment)) {
630 | includeContainment = false;
631 | preferenceApplied = true;
632 | } else {
633 | includeContainment = !minimal;
634 | }
635 |
636 | // include membership?
637 | var includeMembership;
638 | if (document.interactionModel === ldp.DirectContainer && document.hasMemberRelation) {
639 | if (hasPreferInclude(req, ldp.PreferMembership)) {
640 | includeMembership = true;
641 | preferenceApplied = true;
642 | } else if (hasPreferOmit(req, ldp.PreferMembership)) {
643 | includeMembership = false;
644 | preferenceApplied = true;
645 | } else {
646 | includeMembership = !minimal;
647 | }
648 | } else {
649 | includeMembership = false;
650 | }
651 |
652 | if (!includeContainment && !includeMembership) {
653 | // we're done!
654 | callback(null, preferenceApplied);
655 | return;
656 | }
657 |
658 | db.getContainment(document.name, function(err, containment) {
659 | if (err) {
660 | callback(err);
661 | return;
662 | }
663 |
664 | if (containment) {
665 | containment.forEach(function(resource) {
666 | if (includeContainment) {
667 | document.triples.push({
668 | subject: document.name,
669 | predicate: ldp.contains,
670 | object: resource
671 | });
672 | }
673 |
674 | if (includeMembership) {
675 | document.triples.push({
676 | subject: document.membershipResource,
677 | predicate: document.hasMemberRelation,
678 | object: resource
679 | });
680 | }
681 | });
682 | }
683 |
684 | callback(null, preferenceApplied);
685 | });
686 | });
687 | }
688 |
689 | // append 'path' to the end of a uri
690 | // - any query or hash in the uri is removed
691 | // - any special characters like / and ? in 'path' are replaced
692 | function addPath(uri, path) {
693 | uri = uri.split("?")[0].split("#")[0];
694 | if (uri.substr(-1) !== '/') {
695 | uri += '/';
696 | }
697 |
698 | // remove special characters from the string (e.g., '/', '..', '?')
699 | var lastSegment = path.replace(/[^\w\s\-_]/gi, '');
700 | return uri + encodeURIComponent(lastSegment);
701 | }
702 |
703 | // generates and reserves a unique URI with base URI 'container'
704 | function uniqueURI(container, callback) {
705 | var candidate = addPath(container, 'res' + Date.now());
706 | db.reserveURI(candidate, function(err) {
707 | callback(err, candidate);
708 | });
709 | }
710 |
711 | // reserves a unique URI for a new resource. will use slug if available,
712 | // but falls back to the usual naming scheme if slug is already used
713 | function assignURI(container, slug, callback) {
714 | if (slug) {
715 | var candidate = addPath(container, slug);
716 | db.reserveURI(candidate, function(err) {
717 | if (err) {
718 | uniqueURI(container, callback);
719 | } else {
720 | callback(null, candidate);
721 | }
722 | });
723 | } else {
724 | uniqueURI(container, callback);
725 | }
726 | }
727 |
728 | // removes any membership triples from a membership resource before updating
729 | // it in the database
730 | // membership triples are not stored with the resource itself (see db.js)
731 | function removeMembership(document) {
732 | if (document.membershipResourceFor) {
733 | // find the member relations. handle the case where the resource is
734 | // a membership resource for more than one container.
735 | var memberRelations = {};
736 | document.membershipResourceFor.forEach(function(memberPattern) {
737 | if (memberPattern.hasMemberRelation) {
738 | memberRelations[memberPattern.hasMemberRelation] = 1;
739 | }
740 | });
741 |
742 | // now filter the triples
743 | document.triples = document.triples.filter(function(triple) {
744 | // keep the triple if the subject is not the membership
745 | // resource or the predicate is not one of the member relations
746 | return triple.subject !== document.name || !memberRelations[triple.predicate];
747 | });
748 | }
749 | }
750 |
751 | // look for a Link request header indicating the entity uses a ldp:Resource
752 | // interaction model rather than container
753 | function hasResourceLink(req) {
754 | var link = req.get('Link');
755 | // look for links like
756 | // ; rel="type"
757 | // these are also valid
758 | // ;rel=type
759 | // ; rel="type http://example.net/relation/other"
760 | return link &&
761 | /\s*;\s*rel\s*=\s*(("\s*([^"]+\s+)*type(\s+[^"]+)*\s*")|\s*type[\s,;$])/
762 | .test(link);
763 | }
764 |
765 | function hasPreferInclude(req, inclusion) {
766 | return hasPrefer(req, 'include', inclusion);
767 | }
768 |
769 | function hasPreferOmit(req, omission) {
770 | return hasPrefer(req, 'omit', omission);
771 | }
772 |
773 | function hasPrefer(req, token, parameter) {
774 | if (!req) {
775 | return false;
776 | }
777 |
778 | var preferHeader = req.get('Prefer');
779 | if (!preferHeader) {
780 | return false;
781 | }
782 |
783 | // from the LDP prefer parameters, the only charcter we need to escape
784 | // for regular expressions is '.'
785 | // https://dvcs.w3.org/hg/ldpwg/raw-file/default/ldp.html#prefer-parameters
786 | var word = parameter.replace(/\./g, '\\.');
787 |
788 | // construct a regex that matches the preference
789 | var regex =
790 | new RegExp(token + '\\s*=\\s*("\\s*([^"]+\\s+)*' + word + '(\\s+[^"]+)*\\s*"|' + word + '$)');
791 | return regex.test(preferHeader);
792 | }
793 |
794 | // check the consistency of the membership triple pattern if this is a direct container
795 | function isMembershipPatternValid(document) {
796 | if (document.interactionModel !== ldp.DirectContainer) {
797 | // not a direct container, nothing to do
798 | return true;
799 | }
800 |
801 | // must have a membership resouce
802 | if (!document.membershipResource) {
803 | return false;
804 | }
805 |
806 | // must have hasMemberRelation or isMemberOfRelation, but can't have both
807 | if (document.hasMemberRelation) {
808 | return !document.isMemberOfRelation;
809 | }
810 | if (document.isMemberOfRelation) {
811 | return !document.hasMemberRelation;
812 | }
813 |
814 | // no membership triple pattern
815 | return false;
816 | }
817 | };
818 |
--------------------------------------------------------------------------------
/testng/LDPjs-bc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/testng/LDPjs-dc-simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/turtle.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*
18 | * Parses and serializes Turtle (text/turtle) using N3.js.
19 | */
20 |
21 | var N3 = require('n3');
22 | var url = require('url');
23 | var path = require('path');
24 | var media = require('./media.js'); // media types
25 | var ldp = require('./vocab/ldp.js'); // LDP vocabulary
26 | var rdf = require('./vocab/rdf.js'); // RDF vocabulary
27 |
28 | // normalize paths (remove '.' and '..')
29 | function normalize(urlStr) {
30 | var urlObj = url.parse(urlStr);
31 | urlObj.pathname = path.normalize(urlObj.pathname);
32 | return urlObj.format();
33 | }
34 |
35 | exports.serialize = function(triples, callback) {
36 | var writer = N3.Writer();
37 | writer.addTriples(triples);
38 | writer.end(function(err, content) {
39 | callback(err, media.turtle, content);
40 | });
41 | };
42 |
43 | exports.parse = function(req, resourceURI, callback) {
44 | var parser = N3.Parser({ documentURI: resourceURI }), triples = [];
45 | parser.parse(req.rawBody, function(err, triple) {
46 | if (err) {
47 | callback(err);
48 | } else if (triple) {
49 | triple.subject = normalize(triple.subject);
50 | if (N3.Util.isUri(triple.object)) {
51 | triple.object = normalize(triple.object);
52 | }
53 | triples.push(triple);
54 | } else {
55 | // when last triple is null, we're done parsing
56 | callback(null, triples);
57 | }
58 | });
59 | }
60 |
--------------------------------------------------------------------------------
/viz.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*
18 | * Transform the data in the RDF store to the JSON format for a D3.js
19 | * force-directed graph (see public/index.html).
20 | */
21 |
22 | module.exports = function(app, db, env) {
23 | var rdf = require('./vocab/rdf.js');
24 | var ldp = require('./vocab/ldp.js');
25 |
26 | app.get('/v', function(req, res, next) {
27 | console.log('GET ' + req.path);
28 |
29 | db.graphs.find({
30 | deleted: {
31 | $ne: true
32 | },
33 | 'triples.0': {
34 | $exists: true
35 | }
36 | }, {
37 | limit: 1000, // put a reasonable limit on how many we'll process
38 | sort: 'name' // try to get the containers (at least the root container)
39 | }).toArray(function(err, docs) {
40 | if (err) {
41 | console.log(err.stack);
42 | res.send(500);
43 | return;
44 | }
45 |
46 | var nodes = {};
47 | var types = {"none":0};
48 | var nextGroupIdx = 1;
49 | var jsonRes = { nodes: [], links: [] };
50 |
51 | // First build the array of all nodes (resources/graphs),
52 | // keep track of array indexes
53 | docs.forEach(function(d){
54 | var node = {
55 | name: nodeName(d.name),
56 | group: 0
57 | };
58 | var l = jsonRes.nodes.push(node);
59 | nodes[node.name] = l-1;
60 | });
61 |
62 | // Next find all the arcs between resources
63 | docs.forEach(function(d) {
64 | var subName = nodeName(d.name);
65 | if (nodes[subName] === undefined) {
66 | return;
67 | }
68 |
69 | // Add in containment triples, which are stored with the resource
70 | if (d.containedBy) {
71 | var containerName = nodeName(d.containedBy);
72 | if (nodes[containerName] !== undefined) {
73 | jsonRes.links.push({
74 | value: 1, // Always 1, why?
75 | source: nodes[containerName],
76 | target: nodes[subName]
77 | });
78 | }
79 | }
80 |
81 | d.triples.forEach(function(t) {
82 | var objName = nodeName(t.object);
83 | if (nodes[objName] !== undefined) {
84 | jsonRes.links.push({
85 | value: 1, // Always 1, why?
86 | source: nodes[subName],
87 | target: nodes[objName]
88 | });
89 | }
90 |
91 | // Assign a group based on rdf:type, but ignore some
92 | // generic LDP types like ldp:RDFSource
93 | if (t.predicate === rdf.type &&
94 | t.object !== ldp.Resource &&
95 | t.object !== ldp.RDFSource) {
96 | if (!types[t.object]) {
97 | types[t.object] = nextGroupIdx++;
98 | }
99 | jsonRes.nodes[nodes[subName]].group = types[t.object];
100 | if (t.object === ldp.Container) {
101 | jsonRes.nodes[nodes[subName]].img = 'folder.png';
102 | console.log("Found a container " + subName);
103 | }
104 | }
105 | });
106 | });
107 | res.json(jsonRes);
108 | });
109 |
110 | });
111 |
112 | function nodeName(uri) {
113 | return uri.replace(env.appBase, '');
114 | }
115 | };
116 |
--------------------------------------------------------------------------------
/vocab/ldp.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | function define(name, value) {
18 | Object.defineProperty(exports, name, {
19 | value: value,
20 | enumerable: true
21 | });
22 | }
23 |
24 | var ns = 'http://www.w3.org/ns/ldp#';
25 | define('ns', ns);
26 | define('prefix', 'ldp');
27 |
28 | // Resources
29 | define('Resource', ns + 'Resource');
30 | define('RDFSource', ns + 'RDFSource');
31 | define('Container', ns + 'Container');
32 | define('BasicContainer', ns + 'BasicContainer');
33 | define('DirectContainer', ns + 'DirectContainer');
34 |
35 | // Properties
36 | define('contains', ns + 'contains');
37 | define('membershipResource', ns + 'membershipResource');
38 | define('hasMemberRelation', ns + 'hasMemberRelation');
39 | define('isMemberOfRelation', ns + 'isMemberOfRelation');
40 |
41 | // Link relations
42 | define('constrainedBy', ns + 'constrainedBy');
43 |
44 | // Preferences
45 | define('PreferContainment', ns + 'PreferContainment');
46 | define('PreferMembership', ns + 'PreferMembership');
47 | define('PreferMinimalContainer', ns + 'PreferMinimalContainer');
48 | define('PreferEmptyContainer', ns + 'PreferEmptyContainer');
49 |
--------------------------------------------------------------------------------
/vocab/rdf.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 IBM Corporation.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | function define(name, value) {
18 | Object.defineProperty(exports, name, {
19 | value: value,
20 | enumerable: true
21 | });
22 | }
23 |
24 | var ns = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
25 | define('ns', ns);
26 | define('prefix', 'rdf');
27 |
28 | // Resources
29 |
30 | // Properties
31 | define('type', ns + 'type');
32 |
--------------------------------------------------------------------------------