Skip to content

Commit fb7c28e

Browse files
author
Chris Hooks
committed
Added ability to send and receive attachments
1 parent 9a11709 commit fb7c28e

File tree

12 files changed

+207
-141
lines changed

12 files changed

+207
-141
lines changed

src/main/java/com/rusticisoftware/tincan/Attachment.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.security.MessageDigest;
2323
import java.security.NoSuchAlgorithmException;
2424
import java.util.Arrays;
25+
import java.io.IOException;
2526

2627
import com.fasterxml.jackson.databind.JsonNode;
2728
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -50,7 +51,7 @@ public class Attachment extends JSONBase {
5051
private URL fileUrl;
5152
private byte[] content;
5253

53-
public Attachment(JsonNode jsonNode) throws URISyntaxException, MalformedURLException {
54+
public Attachment(JsonNode jsonNode) throws URISyntaxException, MalformedURLException, IOException, NoSuchAlgorithmException {
5455
JsonNode usageTypeNode = jsonNode.path("usageType");
5556
if (! usageTypeNode.isMissingNode()) {
5657
this.setUsageType(new URI(usageTypeNode.textValue()));
@@ -85,6 +86,11 @@ public Attachment(JsonNode jsonNode) throws URISyntaxException, MalformedURLExce
8586
if (! fileUrlNode.isMissingNode()) {
8687
this.setFileUrl(new URL(fileUrlNode.textValue()));
8788
}
89+
90+
JsonNode contentNode = jsonNode.path("content");
91+
if (! contentNode.isMissingNode()) {
92+
this.setContent(contentNode.binaryValue());
93+
}
8894
}
8995

9096

@@ -95,7 +101,6 @@ public void setContent(byte[] content) throws NoSuchAlgorithmException {
95101
digest.update(content);
96102
byte[] hash = digest.digest();
97103
setSha2(new String(Hex.encodeHex(hash)));
98-
99104
}
100105

101106
@Override

src/main/java/com/rusticisoftware/tincan/LRS.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public interface LRS {
2929

3030
StatementLRSResponse saveStatement(Statement statement);
3131
StatementsResultLRSResponse saveStatements(List<Statement> statements);
32-
StatementLRSResponse retrieveStatement(String id);
33-
StatementLRSResponse retrieveVoidedStatement(String id);
32+
StatementLRSResponse retrieveStatement(String id, boolean attachments);
33+
StatementLRSResponse retrieveVoidedStatement(String id, boolean attachments);
3434
StatementsResultLRSResponse queryStatements(StatementsQueryInterface query);
3535
StatementsResultLRSResponse moreStatements(String moreURL);
3636

src/main/java/com/rusticisoftware/tincan/RemoteLRS.java

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@
1717

1818
import javax.servlet.MultipartConfigElement;
1919
import javax.servlet.http.Part;
20-
21-
import com.rusticisoftware.tincan.http.HTTPPart;
2220
import org.apache.commons.codec.binary.Base64;
2321
import com.fasterxml.jackson.core.JsonProcessingException;
2422
import com.fasterxml.jackson.databind.JsonNode;
2523
import com.fasterxml.jackson.databind.node.ArrayNode;
24+
import com.fasterxml.jackson.databind.ObjectMapper;
2625
import com.rusticisoftware.tincan.documents.ActivityProfileDocument;
2726
import com.rusticisoftware.tincan.documents.AgentProfileDocument;
2827
import com.rusticisoftware.tincan.documents.Document;
@@ -34,6 +33,8 @@
3433
import com.rusticisoftware.tincan.json.Mapper;
3534
import com.rusticisoftware.tincan.json.StringOfJSON;
3635
import com.rusticisoftware.tincan.v10x.StatementsQuery;
36+
import com.rusticisoftware.tincan.http.HTTPPart;
37+
import com.rusticisoftware.tincan.internal.MultipartParser;
3738
import org.eclipse.jetty.client.api.ContentResponse;
3839
import org.eclipse.jetty.client.api.Request;
3940
import org.eclipse.jetty.client.api.Response;
@@ -219,22 +220,21 @@ private HTTPResponse makeSyncRequest(HTTPRequest req) {
219220
}
220221
}
221222

222-
223223
OutputStreamContentProvider content = new OutputStreamContentProvider();
224224
InputStreamResponseListener listener = new InputStreamResponseListener();
225225

226-
try (OutputStream output = content.getOutputStream()){
227-
if(req.getPartList() == null || req.getPartList().size() <= 0) {
226+
try (OutputStream output = content.getOutputStream()) {
227+
if (req.getPartList() == null || req.getPartList().size() <= 0) {
228228
if (req.getContentType() != null) {
229229
webReq.header("Content-Type", req.getContentType());
230230
}
231-
else {
231+
else if (req.getContentType() != "GET") {
232232
webReq.header("Content-Type", "application/octet-stream");
233233
}
234234

235235
webReq.content(content).send(listener);
236236

237-
if(req.getContent() != null) {
237+
if (req.getContent() != null) {
238238
output.write(req.getContent());
239239
}
240240

@@ -247,7 +247,6 @@ private HTTPResponse makeSyncRequest(HTTPRequest req) {
247247
webReq.header("Content-Type", "multipart/mixed; boundary=" + multiout.getBoundary());
248248
webReq.content(content).send(listener);
249249

250-
251250
if (req.getContentType() != null) {
252251
multiout.startPart(req.getContentType());
253252
} else {
@@ -263,8 +262,6 @@ private HTTPResponse makeSyncRequest(HTTPRequest req) {
263262
"Content-Transfer-Encoding: binary",
264263
"X-Experience-API-Hash: " + part.getSha2()});
265264
multiout.write(part.getContent());
266-
267-
268265
}
269266
multiout.close();
270267
}
@@ -274,7 +271,7 @@ private HTTPResponse makeSyncRequest(HTTPRequest req) {
274271

275272
response.setStatus(httpResponse.getStatus());
276273
response.setStatusMsg(httpResponse.getReason());
277-
for(HttpField header: httpResponse.getHeaders()) {
274+
for (HttpField header : httpResponse.getHeaders()) {
278275
response.setHeader(header.getName(), header.getValue());
279276
}
280277

@@ -284,23 +281,52 @@ private HTTPResponse makeSyncRequest(HTTPRequest req) {
284281
try (InputStream responseContent = listener.getInputStream())
285282
{
286283

287-
if(response.getContentType() != null && response.getContentType().contains("multipart/mixed")){
284+
if (response.getContentType() != null && response.getContentType().contains("multipart/mixed")) {
288285

289-
MultiPartInputStreamParser multiin = new MultiPartInputStreamParser(responseContent,response.getContentType().replace("multipart/mixed","multipart/form-data"),new MultipartConfigElement("/tmp"),new File("/tmp"));
286+
MultipartParser responseHandler = new MultipartParser(responseContent);
290287

291-
Collection<Part> parts = multiin.getParts();
288+
// We need to get the first part that contains the statements and parse them
289+
responseHandler.nextPart();
292290

293-
for (Part part : parts) {
294-
if(part.getContentType().equals("application/json")){
291+
ArrayList<Statement> statements = new ArrayList<Statement>();
292+
293+
if (responseHandler.getHeaders().get("Content-Type").contains("application/json")) {
294+
JsonNode statementsNode = (new StringOfJSON(new String(responseHandler.getContent())).toJSONNode());
295+
if (! (statementsNode.findPath("statements").isMissingNode())) {
296+
statementsNode = statementsNode.findPath("statements");
297+
for (JsonNode obj : statementsNode) {
298+
statements.add(new Statement(obj));
299+
}
300+
}
301+
else {
302+
statements.add(new Statement(statementsNode));
295303
}
304+
}
296305

306+
while (! responseHandler.noMoreParts) {
307+
responseHandler.nextPart();
308+
String hashToMatch = responseHandler.getHeaders().get("X-Experience-API-Hash");
309+
for (Statement stmt : statements) {
310+
if (stmt.getAttachments() != null) {
311+
for (Attachment a : stmt.getAttachments()) {
312+
if (a.getSha2().equals(hashToMatch)) {
313+
a.setContent(responseHandler.getContent());
314+
break;
315+
}
316+
}
317+
}
318+
}
297319
}
320+
StatementsResult responseStatements = new StatementsResult();
321+
responseStatements.setStatements(statements);
322+
response.setContentBytes(responseStatements.toJSONNode(TCAPIVersion.V101).toString().getBytes());
323+
}
324+
else {
325+
String responseContentString = IO.toString(responseContent);
326+
response.setContentBytes(responseContentString.getBytes());
298327
}
299-
String responseContentString = IO.toString(responseContent);
300-
response.setContentBytes(responseContentString.getBytes());
301328
}
302329

303-
304330
} catch (Exception ex) {
305331
response.setStatus(400);
306332
response.setStatusMsg("Exception in RemoteLRS.makeSyncRequest(): " + ex);
@@ -309,12 +335,11 @@ private HTTPResponse makeSyncRequest(HTTPRequest req) {
309335
return response;
310336
}
311337

312-
private StatementLRSResponse getStatement(String id, String paramName) {
338+
private StatementLRSResponse getStatement(HashMap<String, String> params) {
313339
HTTPRequest request = new HTTPRequest();
314340
request.setMethod(HttpMethod.GET.asString());
315341
request.setResource("statements");
316-
request.setQueryParams(new HashMap<String, String>());
317-
request.getQueryParams().put(paramName, id);
342+
request.setQueryParams(params);
318343

319344
HTTPResponse response = makeSyncRequest(request);
320345
int status = response.getStatus();
@@ -612,14 +637,20 @@ public StatementsResultLRSResponse saveStatements(List<Statement> statements) {
612637
}
613638

614639
@Override
615-
public StatementLRSResponse retrieveStatement(String id) {
616-
return getStatement(id, "statementId");
640+
public StatementLRSResponse retrieveStatement(String id, boolean attachments) {
641+
HashMap<String, String> params = new HashMap<String, String>();
642+
params.put("statementId", id);
643+
params.put("attachments", String.valueOf(attachments));
644+
return getStatement(params);
617645
}
618646

619647
@Override
620-
public StatementLRSResponse retrieveVoidedStatement(String id) {
648+
public StatementLRSResponse retrieveVoidedStatement(String id, boolean attachments) {
621649
String paramName = (this.getVersion() == TCAPIVersion.V095) ? "statementId" : "voidedStatementId";
622-
return getStatement(id, paramName);
650+
HashMap<String, String> params = new HashMap<String, String>();
651+
params.put(paramName, id);
652+
params.put("attachments", String.valueOf(attachments));
653+
return getStatement(params);
623654
}
624655

625656
@Override

src/main/java/com/rusticisoftware/tincan/Statement.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.IOException;
1919
import java.net.MalformedURLException;
2020
import java.net.URISyntaxException;
21+
import java.security.NoSuchAlgorithmException;
2122
import java.util.List;
2223
import java.util.UUID;
2324

@@ -50,7 +51,7 @@ public class Statement extends StatementBase {
5051
@Deprecated
5152
private Boolean voided;
5253

53-
public Statement(JsonNode jsonNode) throws URISyntaxException, MalformedURLException {
54+
public Statement(JsonNode jsonNode) throws URISyntaxException, MalformedURLException, IOException, NoSuchAlgorithmException {
5455
super(jsonNode);
5556

5657
JsonNode idNode = jsonNode.path("id");
@@ -79,7 +80,7 @@ public Statement(JsonNode jsonNode) throws URISyntaxException, MalformedURLExcep
7980
}
8081
}
8182

82-
public Statement(StringOfJSON jsonStr) throws IOException, URISyntaxException {
83+
public Statement(StringOfJSON jsonStr) throws IOException, URISyntaxException, NoSuchAlgorithmException {
8384
this(jsonStr.toJSONNode());
8485
}
8586

src/main/java/com/rusticisoftware/tincan/StatementsResult.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.IOException;
1919
import java.net.MalformedURLException;
2020
import java.net.URISyntaxException;
21+
import java.security.NoSuchAlgorithmException;
2122
import java.util.ArrayList;
2223
import java.util.Iterator;
2324

@@ -42,7 +43,7 @@ public class StatementsResult extends JSONBase {
4243
private ArrayList<Statement> statements = new ArrayList<Statement>();
4344
private String moreURL;
4445

45-
public StatementsResult(JsonNode jsonNode) throws URISyntaxException, MalformedURLException {
46+
public StatementsResult(JsonNode jsonNode) throws URISyntaxException, MalformedURLException, IOException, NoSuchAlgorithmException {
4647
this();
4748

4849
JsonNode statementsNode = jsonNode.path("statements");
@@ -58,7 +59,7 @@ public StatementsResult(JsonNode jsonNode) throws URISyntaxException, MalformedU
5859
}
5960
}
6061

61-
public StatementsResult(StringOfJSON json) throws IOException, URISyntaxException {
62+
public StatementsResult(StringOfJSON json) throws IOException, URISyntaxException, NoSuchAlgorithmException {
6263
this(json.toJSONNode());
6364
}
6465

src/main/java/com/rusticisoftware/tincan/SubStatement.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import java.net.MalformedURLException;
1919
import java.net.URISyntaxException;
20+
import java.io.IOException;
21+
import java.security.NoSuchAlgorithmException;
2022

2123
import lombok.Data;
2224
import lombok.EqualsAndHashCode;
@@ -36,7 +38,7 @@
3638
public class SubStatement extends StatementBase implements StatementTarget {
3739
private final String objectType = "SubStatement";
3840

39-
public SubStatement (JsonNode jsonNode) throws MalformedURLException, URISyntaxException {
41+
public SubStatement (JsonNode jsonNode) throws MalformedURLException, URISyntaxException, IOException, NoSuchAlgorithmException {
4042
super(jsonNode);
4143
}
4244

src/main/java/com/rusticisoftware/tincan/http/HTTPPart.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public class HTTPPart {
2626
private String sha2;
2727
private String contentType;
2828
private byte[] content;
29-
3029
}
3130

3231

src/main/java/com/rusticisoftware/tincan/http/HTTPRequest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.rusticisoftware.tincan.Attachment;
1919
import lombok.Data;
2020
import lombok.EqualsAndHashCode;
21+
import javax.servlet.http.Part;
2122

2223
import java.util.List;
2324
import java.util.Map;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Copyright 2016 Rustici Software
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+
package com.rusticisoftware.tincan.internal;
17+
18+
import java.io.*;
19+
import java.util.*;
20+
import lombok.Data;
21+
22+
@Data
23+
public class MultipartParser {
24+
private InputStream in;
25+
private BufferedReader reader;
26+
private String boundary = "";
27+
private String finalBoundary = "";
28+
private HashMap<String, String> headers = new HashMap<String, String>();
29+
private byte[] content;
30+
public boolean noMoreParts = false;
31+
32+
public MultipartParser(InputStream _in) throws IOException {
33+
this.in = _in;
34+
this.reader = new BufferedReader(new InputStreamReader(this.in));
35+
this.boundary = this.findBoundary();
36+
this.finalBoundary = boundary + "--";
37+
}
38+
39+
public void nextPart() throws IOException {
40+
String line;
41+
while ((line = reader.readLine().trim()).length() > 0) {
42+
String[] parts = line.split(":");
43+
headers.put(parts[0].trim(), parts[1].trim());
44+
}
45+
46+
String contentString = "";
47+
while (! (line = reader.readLine()).equals(boundary)) {
48+
if (line.equals(finalBoundary)) {
49+
noMoreParts = true;
50+
break;
51+
}
52+
contentString += line;
53+
}
54+
55+
content = contentString.getBytes();
56+
}
57+
58+
private String findBoundary() throws IOException {
59+
String line;
60+
while ((line = reader.readLine()) != null) {
61+
if (line.startsWith("--")) {
62+
return line;
63+
}
64+
}
65+
66+
return "";
67+
}
68+
}

0 commit comments

Comments
 (0)