Skip to content

Commit 5e6e102

Browse files
tseabrooksChris Hooks
authored andcommitted
Adds functionality for sending attachments. No functionality for Recieving attachments.
1 parent 0fc6101 commit 5e6e102

File tree

11 files changed

+356
-31
lines changed

11 files changed

+356
-31
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@
163163
<artifactId>jetty-client</artifactId>
164164
<version>9.3.6.v20151106</version>
165165
</dependency>
166+
<dependency>
167+
<groupId>org.eclipse.jetty</groupId>
168+
<artifactId>jetty-servlet</artifactId>
169+
<version>9.3.6.v20151106</version>
170+
</dependency>
166171
</dependencies>
167172

168173
<properties>

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@
1919
import java.net.URI;
2020
import java.net.URISyntaxException;
2121
import java.net.URL;
22+
import java.security.MessageDigest;
23+
import java.security.NoSuchAlgorithmException;
24+
import java.util.Arrays;
2225

2326
import com.fasterxml.jackson.databind.JsonNode;
2427
import com.fasterxml.jackson.databind.node.ObjectNode;
28+
import com.rusticisoftware.tincan.http.HTTPPart;
2529
import com.rusticisoftware.tincan.json.JSONBase;
2630
import com.rusticisoftware.tincan.json.Mapper;
2731

2832
import lombok.Data;
2933
import lombok.EqualsAndHashCode;
3034
import lombok.NoArgsConstructor;
35+
import org.apache.commons.codec.binary.Hex;
3136

3237
/**
3338
* Attachment Class
@@ -43,6 +48,7 @@ public class Attachment extends JSONBase {
4348
private Integer length;
4449
private String sha2;
4550
private URL fileUrl;
51+
private byte[] content;
4652

4753
public Attachment(JsonNode jsonNode) throws URISyntaxException, MalformedURLException {
4854
JsonNode usageTypeNode = jsonNode.path("usageType");
@@ -80,7 +86,18 @@ public Attachment(JsonNode jsonNode) throws URISyntaxException, MalformedURLExce
8086
this.setFileUrl(new URL(fileUrlNode.textValue()));
8187
}
8288
}
83-
89+
90+
91+
public void setContent(byte[] content) throws NoSuchAlgorithmException {
92+
this.content = Arrays.copyOf(content, content.length);
93+
setLength(content.length);
94+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
95+
digest.update(content);
96+
byte[] hash = digest.digest();
97+
setSha2(new String(Hex.encodeHex(hash)));
98+
99+
}
100+
84101
@Override
85102
public ObjectNode toJSONNode(TCAPIVersion version) {
86103
ObjectNode node = Mapper.getInstance().createObjectNode();
@@ -107,4 +124,12 @@ public ObjectNode toJSONNode(TCAPIVersion version) {
107124
}
108125
return node;
109126
}
127+
128+
public HTTPPart getPart(){
129+
HTTPPart part = new HTTPPart();
130+
part.setContent(content);
131+
part.setContentType(contentType);
132+
part.setSha2(sha2);
133+
return part;
134+
}
110135
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public interface LRS {
3434
StatementsResultLRSResponse queryStatements(StatementsQueryInterface query);
3535
StatementsResultLRSResponse moreStatements(String moreURL);
3636

37+
3738
ProfileKeysLRSResponse retrieveStateIds(Activity activity, Agent agent, UUID registration);
3839
StateLRSResponse retrieveState(String id, Activity activity, Agent agent, UUID registration);
3940
LRSResponse saveState(StateDocument state);

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

Lines changed: 106 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package com.rusticisoftware.tincan;
1717

18+
import javax.servlet.MultipartConfigElement;
19+
import javax.servlet.http.Part;
20+
21+
import com.rusticisoftware.tincan.http.HTTPPart;
1822
import org.apache.commons.codec.binary.Base64;
1923
import com.fasterxml.jackson.core.JsonProcessingException;
2024
import com.fasterxml.jackson.databind.JsonNode;
@@ -34,20 +38,23 @@
3438
import org.eclipse.jetty.client.api.Request;
3539
import org.eclipse.jetty.client.api.Response;
3640
import org.eclipse.jetty.client.HttpClient;
37-
import org.eclipse.jetty.client.util.BytesContentProvider;
41+
import org.eclipse.jetty.client.api.Response.CompleteListener;
42+
import org.eclipse.jetty.client.api.Response.Listener;
43+
import org.eclipse.jetty.client.util.*;
3844
import org.eclipse.jetty.http.HttpField;
3945
import org.eclipse.jetty.http.HttpMethod;
40-
import org.eclipse.jetty.util.HttpCookieStore;
46+
import org.eclipse.jetty.util.*;
4147
import org.eclipse.jetty.util.ssl.SslContextFactory;
4248
import lombok.Data;
4349
import lombok.NoArgsConstructor;
4450

45-
import java.io.IOException;
46-
import java.io.UnsupportedEncodingException;
51+
import java.io.*;
4752
import java.net.MalformedURLException;
4853
import java.net.URL;
4954
import java.net.URLEncoder;
55+
import java.nio.ByteBuffer;
5056
import java.util.*;
57+
import java.util.concurrent.TimeUnit;
5158

5259
/**
5360
* Class used to communicate with a TCAPI endpoint synchronously
@@ -192,25 +199,14 @@ private HTTPResponse makeSyncRequest(HTTPRequest req) {
192199
}
193200
}
194201

195-
//Overload some of an ContentExchange object's functions with anonymous inner functions
196-
//Access the HTTPResponse variable via a closure
202+
197203
final HTTPResponse response = new HTTPResponse();
198204

199205
try {
200206
final Request webReq = httpClient().
201207
newRequest(url).
202208
method(HttpMethod.fromString(req.getMethod())).
203-
header("X-Experience-API-Version", this.version.toString()).
204-
onResponseHeaders(new Response.HeadersListener() {
205-
@Override
206-
public void onHeaders(Response webResp) {
207-
response.setStatus(webResp.getStatus());
208-
response.setStatusMsg(webResp.getReason());
209-
for(HttpField header: webResp.getHeaders()) {
210-
response.setHeader(header.getName(), header.getValue());
211-
}
212-
}
213-
});
209+
header("X-Experience-API-Version", this.version.toString());
214210

215211
if (this.auth != null) {
216212
webReq.header("Authorization", this.auth);
@@ -223,19 +219,88 @@ public void onHeaders(Response webResp) {
223219
}
224220
}
225221

226-
if (req.getContentType() != null) {
227-
webReq.header("Content-Type", req.getContentType());
222+
223+
OutputStreamContentProvider content = new OutputStreamContentProvider();
224+
InputStreamResponseListener listener = new InputStreamResponseListener();
225+
226+
try (OutputStream output = content.getOutputStream()){
227+
if(req.getPartList() == null || req.getPartList().size() <= 0) {
228+
if (req.getContentType() != null) {
229+
webReq.header("Content-Type", req.getContentType());
230+
}
231+
else {
232+
webReq.header("Content-Type", "application/octet-stream");
233+
}
234+
235+
webReq.content(content).send(listener);
236+
237+
if(req.getContent() != null) {
238+
output.write(req.getContent());
239+
}
240+
241+
output.close();
242+
}
243+
else {
244+
245+
MultiPartOutputStream multiout = new MultiPartOutputStream(output);
246+
247+
webReq.header("Content-Type", "multipart/mixed; boundary=" + multiout.getBoundary());
248+
webReq.content(content).send(listener);
249+
250+
251+
if (req.getContentType() != null) {
252+
multiout.startPart(req.getContentType());
253+
} else {
254+
multiout.startPart("application/octet-stream");
255+
}
256+
257+
if (req.getContent() != null) {
258+
multiout.write(req.getContent());
259+
}
260+
261+
for (HTTPPart part : req.getPartList()) {
262+
multiout.startPart(part.getContentType(), new String[]{
263+
"Content-Transfer-Encoding: binary",
264+
"X-Experience-API-Hash: " + part.getSha2()});
265+
multiout.write(part.getContent());
266+
267+
268+
}
269+
multiout.close();
270+
}
228271
}
229-
else {
230-
webReq.header("Content-Type", "application/octet-stream");
272+
273+
Response httpResponse = listener.get(5, TimeUnit.SECONDS);
274+
275+
response.setStatus(httpResponse.getStatus());
276+
response.setStatusMsg(httpResponse.getReason());
277+
for(HttpField header: httpResponse.getHeaders()) {
278+
response.setHeader(header.getName(), header.getValue());
231279
}
232280

233-
if (req.getContent() != null) {
234-
webReq.content(new BytesContentProvider(req.getContent()));
281+
282+
283+
// Use try-with-resources to close input stream.
284+
try (InputStream responseContent = listener.getInputStream())
285+
{
286+
287+
if(response.getContentType() != null && response.getContentType().contains("multipart/mixed")){
288+
289+
MultiPartInputStreamParser multiin = new MultiPartInputStreamParser(responseContent,response.getContentType().replace("multipart/mixed","multipart/form-data"),new MultipartConfigElement("/tmp"),new File("/tmp"));
290+
291+
Collection<Part> parts = multiin.getParts();
292+
293+
for (Part part : parts) {
294+
if(part.getContentType().equals("application/json")){
295+
}
296+
297+
}
298+
}
299+
String responseContentString = IO.toString(responseContent);
300+
response.setContentBytes(responseContentString.getBytes());
235301
}
236302

237-
final ContentResponse webResp = webReq.send();
238-
response.setContentBytes(webResp.getContent());
303+
239304
} catch (Exception ex) {
240305
response.setStatus(400);
241306
response.setStatusMsg("Exception in RemoteLRS.makeSyncRequest(): " + ex);
@@ -445,6 +510,10 @@ public StatementLRSResponse saveStatement(Statement statement) {
445510
return lrsResponse;
446511
}
447512

513+
if (statement.hasAttachmentsWithContent()) {
514+
lrsResponse.getRequest().setPartList(statement.getPartList());
515+
}
516+
448517
if (statement.getId() == null) {
449518
lrsResponse.getRequest().setMethod(HttpMethod.POST.asString());
450519
}
@@ -504,6 +573,18 @@ public StatementsResultLRSResponse saveStatements(List<Statement> statements) {
504573
return lrsResponse;
505574
}
506575

576+
for (Statement statement: statements) {
577+
if (statement.hasAttachmentsWithContent()) {
578+
if(lrsResponse.getRequest().getPartList() == null){
579+
lrsResponse.getRequest().setPartList(statement.getPartList());
580+
}
581+
else{
582+
lrsResponse.getRequest().getPartList().addAll(statement.getPartList());
583+
}
584+
}
585+
}
586+
587+
507588
HTTPResponse response = makeSyncRequest(lrsResponse.getRequest());
508589
int status = response.getStatus();
509590

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.List;
2222
import java.util.UUID;
2323

24+
import com.rusticisoftware.tincan.http.HTTPPart;
2425
import lombok.Data;
2526
import lombok.EqualsAndHashCode;
2627
import lombok.NoArgsConstructor;
@@ -133,4 +134,6 @@ public void stamp() {
133134
this.setTimestamp(new DateTime());
134135
}
135136
}
137+
138+
136139
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*/
1616
package com.rusticisoftware.tincan.http;
1717

18+
import com.rusticisoftware.tincan.Attachment;
1819
import lombok.Data;
1920
import lombok.EqualsAndHashCode;
2021

22+
import java.util.List;
2123
import java.util.Map;
2224

2325
/**
@@ -33,4 +35,5 @@ public class HTTPRequest {
3335
private String contentType;
3436
private byte[] content;
3537
private HTTPResponse response;
38+
private List<HTTPPart> partList;
3639
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.joda.time.format.DateTimeFormatter;
2323

2424
import java.util.HashMap;
25+
import java.util.List;
2526

2627
/**
2728
* HTTPResponse Class Description
@@ -33,6 +34,7 @@ public class HTTPResponse {
3334
private String statusMsg;
3435
private final HashMap<String,String> headers = new HashMap<String, String>();
3536
private byte[] contentBytes;
37+
private List<HTTPPart> partList;
3638

3739
public String getHeader(String key) {
3840
return this.headers.get(key);

src/main/java/com/rusticisoftware/tincan/internal/StatementBase.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.UUID;
2424

25+
import com.rusticisoftware.tincan.http.HTTPPart;
2526
import lombok.Data;
2627
import lombok.EqualsAndHashCode;
2728
import lombok.NoArgsConstructor;
@@ -166,4 +167,41 @@ public ObjectNode toJSONNode(TCAPIVersion version) {
166167

167168
return node;
168169
}
170+
171+
public boolean hasAttachments(){
172+
return (attachments != null && attachments.size() > 0);
173+
}
174+
175+
public boolean hasAttachmentsWithContent(){
176+
if(attachments != null) {
177+
for (Attachment attachment : attachments) {
178+
if (attachment.getContent().length > 0) {
179+
return true;
180+
}
181+
182+
}
183+
}
184+
return false;
185+
}
186+
187+
public void addAttachment(Attachment attachment){
188+
if(attachments == null){
189+
attachments = new ArrayList<Attachment>();
190+
}
191+
attachments.add(attachment);
192+
}
193+
public void addAttachments(Attachment attachments){
194+
if(this.attachments == null){
195+
this.attachments = new ArrayList<Attachment>();
196+
}
197+
this.attachments.add(attachments);
198+
}
199+
200+
public List<HTTPPart> getPartList(){
201+
List<HTTPPart> partList = new ArrayList<HTTPPart>();
202+
for (Attachment attachment:attachments) {
203+
partList.add(attachment.getPart());
204+
}
205+
return partList;
206+
}
169207
}

src/main/java/com/rusticisoftware/tincan/v10x/StatementsQuery.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ public class StatementsQuery implements StatementsQueryInterface {
5454
private DateTime until;
5555
private Integer limit;
5656
private QueryResultFormat format;
57-
//TODO: Expose when attachments are supported here
58-
//private Boolean attachments;
57+
private Boolean attachments;
5958
private Boolean ascending;
6059

6160
public void setVerbID(String verbID) throws URISyntaxException {
@@ -104,6 +103,11 @@ public HashMap<String,String> toParameterMap() throws IOException {
104103
params.put("ascending", this.getAscending().toString());
105104
}
106105

106+
if (this.getAttachments() != null) {
107+
108+
params.put("attachments", this.getAttachments().toString());
109+
}
110+
107111
return params;
108112
}
109113
}

0 commit comments

Comments
 (0)