documentId
To upload a document to an E-Archive, you first need to construct a documentId. The documentId is an identifier that is used both as the key to retrieve a document, and for duplicate/transaction control during upload. It is therefore vital that it is really unique.
The documentId must be 26 characters, and start with an 'X' (for "external", as opposed to internal id's generated by Nets), followed by the organization id. The remaining characters should be used to make it unique. A typical solution is to use a sequence number. Random numbers should not be used, as there will be a chance of collission with an existing id. The chance of a collision is high, especially with a large number of documents, due to the Birthday paradox. Here is how a documentId can be generated in Java.
public class DocumentIdFactory {
static String ORGANIZATION = "912341234";
public static String createDocumentId(int sequenceNumber) {
int uniquePartLength = 26 - ORGANIZATION.length() - 1;
String uniquePart = zeroPad(sequenceNumber, uniquePartLength);
return 'X' + ORGANIZATION + uniquePart;
}
private static String zeroPad(int number, int uniquePartLength) {
String formatString = "%0" + uniquePartLength + "d";
return String.format(formatString, number);
}
public static void main(String[] args) {
System.out.println(createDocumentId(42));
}
}
Document/Object
The document must be in one of the supported document formats, and must also be of a manageable size (there is no hard limit, but up to 50mb is supported. Above that, and you risk running into problems).
Metadata
When a document is uploaded, all the mandatory metadata fields defined for the archive must be specified. All non-modifiable field must also have their value specified in the upload call. Modifiable metadata fields that are not mandatory can be omitted and added later, although normally it is advisable to include all in the upload call.
Upload call
The upload call is a PUT request with a multipart body containing both the document and the metadata values as separate fields. For the metadata fields, the field name is used as the part name, while the part name for the document is content. The content part needs to have the correct content type for the document, while the metadata fields should be "text/plain" and preferably with a character set modifier. The valid content types are listed in Document formats.
Metadata field values are restricted to ISO-8859-1. It is recommended to test that ones implementation of upload properly preserves characters outside of US-ASCII.
Verifying the response
It is important to verify the specific E-Archive response headers, as a 200 OK response could also originate in an intercepting proxy, in which case there would be no guarantee that the document was actually stored.
The following partial code demonstrates uploading a document using Java.
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import static org.apache.http.Consts.UTF_8;
public class StoreService {
private final URL baseUrl;
private final HttpClient http;
public StoreService(URL baseUrl, HttpClient http) {
this.baseUrl = baseUrl;
this.http = http;
}
public void store(byte[] content, ContentType contentType, Map<String, String[]> metadata, String userId, String trackingCode)
throws IOException, InvalidStatusCodeException, DuplicateDocumentException, ArchiveCallFailedException
{
String documentId = metadata.get("documentId")[0];
String objectUrl = createURL(userId, trackingCode, documentId);
HttpEntity body = createMultipartBody(content, contentType, metadata);
HttpPut put = new HttpPut(objectUrl);
put.setEntity(body);
HttpResponse response = http.execute(put);
int statusCode = response.getStatusLine().getStatusCode();
EntityUtils.consume(response.getEntity());
ArchiveHeaders archiveHeaders = new ArchiveHeaders(response);
HelperMethods.validateStatusCodeIsGenuine(statusCode, archiveHeaders);
validateResult(documentId, statusCode, archiveHeaders);
}
private static void validateResult(String documentId, int statusCode, ArchiveHeaders archiveHeaders) throws DuplicateDocumentException, ArchiveCallFailedException {
switch (statusCode) {
case 200:
validateInfoCode(archiveHeaders);
break;
case 409:
throw new DuplicateDocumentException("A document with documentId=" + documentId + " already exists", documentId);
default:
archiveHeaders.printTo(System.err);
throw new ArchiveCallFailedException("The call failed with HTTP status code " + statusCode, statusCode);
}
}
private static HttpEntity createMultipartBody(byte[] content, ContentType contentType, Map<String, String[]> metadata) {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setCharset(UTF_8);
multipartEntityBuilder.addBinaryBody("content", content, contentType, "content.file");
for (String key : metadata.keySet()) {
for (String value : metadata.get(key)) {
multipartEntityBuilder.addTextBody(key, value);
}
}
return multipartEntityBuilder.build();
}
private String createURL(String userId, String trackingCode, String documentId) {
String objectUrl = baseUrl + "/object/" + documentId + "?_userId=" + userId + "&_trackingCode=" + trackingCode;
System.out.println("Putting to: " + objectUrl);
return objectUrl;
}
private static void validateInfoCode(ArchiveHeaders archiveHeaders) {
if ("-1000".equals(archiveHeaders.infoCode)) {
System.out.println("Upload succeeded");
} else {
System.err.println("Confirmation header missing or invalid, expected X-Archive-Info-Code to be -1000");
}
}
}