CSJCurrent en:Migration from Java API v3 to v5.0
Introduction
In this article, you will learn how to migrate an existing project that uses the Cryptshare Java API version 3 to the latest version of the Cryptshare Java API. This article is a technical article that lists the changes for people who are familiar with using the previous Java API version. For general information about the changes introduced in version 5, see the update notes (link TBD).
Preparation
Installing the new Java API Version
For information on installing the new Java API version into your development system, click here for more information.
Migrating existing projects
Replace dependency in the dependency management system
The GroupID and the ArtifactID of the Java API have changed. First, please replace the dependency entry in your dependency management, e.g. in the pom.xml as follows:
Old <dependency> <groupId>com.befinesolutions.cryptshare</groupId> <artifactId>java-api-ng</artifactId> <version>3.X.X</version> </dependency> New <dependency> <groupId>com.cryptshare.api</groupId> <artifactId>cryptshare-api</artifactId> <version>5.0.1.190</version> </dependency>
Incompatibility with existing client.store
The structure of the `client.store` file has been fundamentally revised. Thus, it is unfortunately not possible to continue using the existing `client.store` together with its verifications. Before you test your project with the new version of the Java API, delete the existing `client.store` file first.
com.cryptshare.api.ClientException: Can't read from the client store! at com.cryptshare.api.Client.openStore(Client.java:810) at com.cryptshare.api.Client.<init>(Client.java:126) at com.cryptshare.api.Client.<init>(Client.java:86) at com.befinesolutions.examples.api.ExampleMain.connectWithStore(ExampleMain.java:351) at com.befinesolutions.examples.api.ExampleMain.main(ExampleMain.java:48) Caused by: com.cryptshare.api.ClientException: Unknown error at com.cryptshare.api.DpApiJNAWrapper.unprotect(DpApiJNAWrapper.java:57) at com.cryptshare.api.DpApiProtectionService.unprotect(DpApiProtectionService.java:74) at com.cryptshare.api.ProtectedFileStore.load(ProtectedFileStore.java:130) at com.cryptshare.api.ProtectedFileStore.open(ProtectedFileStore.java:108) at com.cryptshare.api.ProtectedFileStore.open(ProtectedFileStore.java:83) at com.cryptshare.api.Client.openStore(Client.java:808) ... 4 more Caused by: com.sun.jna.platform.win32.Win32Exception: Wrong parameter. at com.sun.jna.platform.win32.Crypt32Util.cryptUnprotectData(Crypt32Util.java:144) at com.cryptshare.api.DpApiJNAWrapper.unprotect(DpApiJNAWrapper.java:55) ... 9 more
Changed package name
The package name of the used classes has changed. Next, in the import statements of your classes, please replace the package name:
Old com.befinesolutions.cryptshare.aping New com.cryptshare.api
Changes in exception handling
To better identify the source of possible problems when using the API, CryptshareServerExceptions are now thrown instead of ClientExceptions in some cases. A ClientException is usually thrown in cases when an operation failed during preparation. CryptshareServerExceptions occur when the operation itself has failed on the Cryptshare server, when incorrect information has been detected, or processing or other errors have occurred.
Changes in the verification process
Initializing a Client
When creating a `Client` object, a string containing the path to the `client.store` file was previously required. Instead, a `Path` object containing the path to the client.store is now required, e.g. by `Paths.get(stringPath)`.
Using a byte array as client store
In case a byte array was used instead of a `client.store` file, a custom implementation of the new interface `IStore` must now be used. You can find more information in the article Provide your own Client Store.
Example implementation
Java API 3 // Get client store from your custom store. byte[] clientStoreBytes = // TODO: Load bytes from your custom store CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL)) Client client = new Client("sender@domain.com", connection, clientStoreBytes); // do something like verifications, transfers, ... // Write encrypted modified client store back into the custom store. byte[] modifiedClientStoreBytes = client.getStore().save(); // TODO: Save modifiedClientStoreBytes to your custom store
The possibility to specify a byte array when creating a client object has been removed. Instead, an implementation of the IStore interface is required. In the following example a minimal implementation has been implemented. Please note the places marked with TODO. These places indicate places where you would need to check if your operating system used is the same as the example or where you need to implement your own load and store methods that you have already used so far in your program using the Cryptshare Java API v3.
Java API 5 CustomStore import com.cryptshare.api.*; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; /** * Implementation of {@link IStore} that persists to a custom source, and encrypts the store. */ public class CustomProtectedStore implements IStore { private final Map<String, String> storeValues; private final IProtectionService protectionService; private CustomProtectedStore(IProtectionService protectionService) { this.protectionService = protectionService; this.storeValues = new HashMap<>(); } /** * A default {@link IProtectionService} is used to protect the store: * AES encryption, using {@link AesProtectionService} with {@link LinuxKeySourceProvider}. * * @return An instance of a {@link CustomProtectedStore}, with all values loaded. * @throws ClientException If opening fails. */ public static CustomProtectedStore open() throws ClientException { // TODO: When your program is run under Windows instead, use the DpApiProtectionService instead of the {@link AesProtectionService}. IProtectionService protectionService = new AesProtectionService(new LinuxKeySourceProvider()); CustomProtectedStore protectedFileStore = new CustomProtectedStore(protectionService); protectedFileStore.load(); return protectedFileStore; } /** * Read from client store */ private void load() throws ClientException { try { byte[] protectedContent = loadStore(); if (protectedContent.length == 0) { return; } byte[] unprotectedContent = protectionService.unprotect(protectedContent); String storeContent = new String(unprotectedContent, StandardCharsets.UTF_8); String[] entries = storeContent.split(";"); for (String entry : entries) { String key = entry.substring(0, entry.indexOf('=')); String value = entry.substring(entry.indexOf('=') + 1); storeValues.put(new String(Hex.decodeHex(key)), value); } } catch (DecoderException e) { throw new ClientException(ClientException.CODE_STORE_ACCESS_FAILED, "Could not read from the client store!", e); } } /** * Gets a stored value from the store. * * @param key Unique key to identify the value. If null, the method returns null. * @return The value that corresponds to the given key, or null, if the key was null or not found. */ @Override public synchronized String getValue(String key) { if (key == null) { return null; } return storeValues.get(key); } /** * Sets a value in the store. * * @param key Unique key for the value. If null, the method call has no effect. * @param value Value to be stored. If null, the method call has no effect. */ @Override public synchronized void setValue(String key, String value) { if (key == null || value == null) { return; } storeValues.put(key, value); } /** * Removes a specific value from the store. * * @param key Unique key to identify the value. If null, the method call has no effect. */ @Override public synchronized void removeValue(String key) { if (key == null) { return; } storeValues.remove(key); } /** * Saves this store object to the custom store. */ @Override public synchronized void persist() throws ClientException { StringBuilder content = new StringBuilder(); storeValues.forEach((k, v) -> { content.append(Hex.encodeHexString(k.getBytes())); content.append("="); content.append(v); content.append(";"); }); byte[] unprotectedContent = content.toString().getBytes(StandardCharsets.UTF_8); byte[] protectedContent = protectionService.protect(unprotectedContent); saveStore(protectedContent); } private byte[] loadStore() { // TODO: Load bytes from your custom store, e.g. from a file or database. } private void saveStore(byte[] bytes) { // TODO: Save bytes to your custom store, e.g. to a file or database. } } Java API 5 // Get client store from your custom store. CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL)) Client client = new Client("sender@domain.com", Locale.ENGLISH, connection, CustomProtectedStore.open()); // do something like verifications, transfers, ... // Write encrypted modified client store back into the custom store. // Info: This step is not necessary anymore.
Verification
The check for the current verification status has changed. The `Client#checkVerification` method now returns a `CheckVerificationResult` object. Whether the client is verified is now checked with `CheckVerificationResult#isUserVerified` instead of `VerificationStatus#isVerified`. The verification mode is obtained with `CheckVerificationResult#getVerificationMode` instead of `VerificationStatus#isSenderVerification`. Below you can see the verification process in detail for both verification modes:
Client Verification
private final String SENDER = "john.doe@example.com"; private final String SERVER_URL = "https://cryptshare.mydomain.com"; private final String CLIENT_STORE_PATH = "client.store"; CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL)); Client client = new Client(SENDER, connection, Paths.get(CLIENT_STORE_PATH)); System.out.println("Checking whether the client is verified..."); CheckVerificationResult result = client.checkVerification(); if (result.isUserVerified()) { System.out.println("The client is verified; nothing to do."); } else { if (result.getVerificationMode() == VerificationMode.SENDER) { System.out.println("Verification mode is set to 'Sender', please set it to 'Client' and add the following Client ID: " + client.getClientId()); return; } try { client.RequestClientVerification(); System.out.println("The client verification has been requested. Please rerun the program and check whether your client is verified."); } catch (CryptshareServerException e) { System.out.println("The verification mode is set to 'Client', but your Client ID is not whitelisted. " + "Please add your Client ID '" + client.getClientId() + "' to the list of allowed clients."); return; } }
The method `Client#checkVerification` now returns another type of object that also has to be used differently for checking for an existing verification. Instead of calling `Client#requestVerification` a client verification has to be requested explicitely by `Client#requestClientVerification`. In case the client is not verified yet `Client#requestClientVerification` throws an exception that has to be handled.
Sender Verification
private final String SENDER = "john.doe@example.com"; private final String SERVER_URL = "https://cryptshare.mydomain.com"; private final String CLIENT_STORE_PATH = "client.store"; CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL)); Client client = new Client(SENDER, connection, Paths.get(CLIENT_STORE_PATH)); System.out.println("Checking whether the client is verified..."); CheckVerificationResult result = client.checkVerification(); if (result.isUserVerified()) { System.out.println("The client is verified; nothing to do."); } else { if (result.getVerificationMode() == VerificationMode.CLIENT) { System.out.println("Verification mode is set to 'Client', please set it to 'Sender'."); return; } client.RequestSenderVerification(); System.out.println("Please type in the verification code:"); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String vericode = reader.readLine(); reader.close(); // now tell the client to store the verification code in the verification store client.confirmSenderVerification(vericode.trim()); }
The method `Client#checkVerification` now returns another type of object that also has to be used differently for checking for an existing verification. Instead of calling `Client#requestVerification` a sender verification has to be requested explicitely by `Client#requestSenderVerification`. Saving the verification code does not work via `Client#storeVerification`, but with the method `Client#confirmSenderVerification`.
Changed transfer process
Preparing a transfer
Methods for preparing a transfer were renamed or require new data types as paremeters.:
Description | Old Method | New Method | Change |
---|---|---|---|
Should the server send a download notification to the sender of the transfer? | Transfer#setDownloadNotification | Transfer#setInformAboutDownload | New method name |
Setting the language for sender notification emails | Transfer#setSenderLanguage | Client#setUserLanguage | New method name and the language is set for the client instead of the transfer. |
Setting transfer recipients | Transfer#addRecipient
Transfer#setRecipients Transfer#addRecipients |
These methods now require `Recipient` objects instead of strings, e.g.
`transfer.addRecipient("john.doe@cryptshare.com")` will be replaced by `transfer.addRecipient(new Recipient("john.doe@cryptshare,com"))` | |
Setting the expiration date | Transfer#setExpirationDate(Date) | Transfer#setExpirationDate(LocalDate) | This method now requires a LocalDate object instead of a Date object, e.g.
LocalDate.of(2020, 12, 24); |
Setting the recipient language | Transfer#setRecipientLanguage(String) | Transfer#setRecipientLanguage(Locale) | This method now requires a Locale object instead of a string, e.g. Locale.ENGLISH. |
Generate a random password | Client#requestPassword(int)
Client#requestPassword(int, boolean) |
Client#requestGeneratedPassword(int) | New method name.
The local generation of passwords is not possible anymore. |
Setting a generated password | Transfer#setGeneratedPassword | Transfer#setPassword | Automatically generated passwords have to set exactly as manual passwords. |
Creating objects for transfer files | TransferFile(String) | TransferFile(Path) | The creation of TransferFile objects now require a Path object instead of a string, e.g. Paths.get(pathString). |
Adding transfer files to a transfer | Transfer#setFiles | Transfer#setTransferFiles | New method name |
Perform a transfer
The execution of asynchronous and synchronous transfers has been adjusted by not requiring only one class that takes care of all possible state changes of the transfer such as upload progress change, upload finished, upload interrupted and transfer aborted. Instead, a separate handler can now be defined for each of these state changes. These derive their names from the following interfaces: IUploadProgressChangedHandler, IUploadCompleteHandler, IUploadInterruptedHandler, IUploadCancelledHandler. These each require a method to be implemented. The method to be implemented directly provides all available information, so it is no longer necessary to extract it from an event object. If a handler is not needed, null can be specified for that handler when starting a transfer. Examples of handlers:
public class UploadProgressChangedHandler implements IUploadProgressChangedHandler { @Override public void progressChanged(String currentFileNo, String currentFileName, double bytesUploaded, double bytesTotal, long bytesPerSecond) { // do something } } public class UploadCompleteHandler implements IUploadCompleteHandler { @Override public void uploadComplete(Map<String, String> urlMappings, Map<String, String> smtpMappings, String serverGenPassword, TransferError transferError, String trackingId) { // do something } } public class UploadInterruptedHandler implements IUploadInterruptedHandler { @Override public void uploadInterrupted(CryptshareException cryptshareException) { // do something } } public class UploadCancelledHeader implements IUploadCancelledHandler { @Override public void cancelled() { // do something } }
Usage of handler during starting a transfer:
// Asynchronous Transfer client.beginTransfer(transfer, new UploadProgressChangedHandler(), new UploadCompleteHandler(), new UploadInterruptedHandler(), new UploadCancelledHeader(), pollingTimer); // Synchronous Transfer client.performTransfer(transfer, new UploadProgressChangedHandler(), new UploadCompleteHandler(), new UploadInterruptedHandler(), new UploadCancelledHeader(), pollingTimer);
The new integer parameter pollingTimer specifies in milliseconds how often the server should be polled for the current state of the running transfer, e.g. every 1000 milliseconds (= 1 second).
Changes in other methods
Requesting general server data
Client#requestTransferData was removed. Please use Client#requestPolicy instead to determine the valid settings for your sender-recipient combination.
List<String> recipients = new ArrayList<>(); recipients.add("user001@cryptshare.com"); recipients.add("user002@cryptshare.com"); Policy policy = client.requestPolicy(recipients);
Policy rules
Names and data types of some policy information fields have changed:
Description | Old Method | New Method | Change |
---|---|---|---|
Allowed standard password modes | Policy#getPasswordMode | Policy#getAllowedStandardPasswordModes | New method name and changed data type to `Set` instead of a `List`. |
Recipients that are now allowed for the given sender | Policy#getFailedAddresses | Policy#getFailedEmailAddresses | New method name and changed data type to `Set` instead of a `List`. |
Language Resources
Retrieving defined language pack versions is no longer possible. Instead, use those methods that do not specify the language package version, e.g. Client#requestLanguagePacks() and Client#requestLanguagePackFile(String, Locale).
Description | Old Method | New Method | Change |
---|---|---|---|
Determine the language pack version | LanguagePack#getLanguagePackVersion | LanguagePack#getVersion | New method name. |
Determine the last change for this language pack | LanguagePack#getLastUpdate | Method now returns a ZoneDateTime object instead of a Unix timestamp in the Long format. |
Terms of Use
The return type of the terms of use using Client#requestTermsOfUse has changed. This method now returns a TermsOfUse object. You can get the terms of use specific to a language via termsOfUse#getTextByLanguage(Locale).
License Information
The data type of individual fields within the LicenseInfo object queried via Client#requestLicenseInfo have changed from String to LocalDate: LicenseInfo#getProductLicenseExpirationDate, LicenseInfo#getProductSubscriptionExpirationDate, LicenseInfo#getServerLicenseExpirationDate, and LicenseInfo#getServerSubscriptionExpirationDate.
The method Client#requestMailTemplate(String, Map<Set<String>, Set<TemplateReplacement>, <language>, <mailFormat>>, Locale, String) now requires another new last parameter of type EmailFormat. Example:
Old Map<List<String>, String> mailTemplates = client.requestMailTemplate("recipient", replacements, new Locale("ja"), "html"); New Map<List<String>, String> mailTemplates = client.requestMailTemplate("recipient", replacements, new Locale("ja"), EmailFormat.HTML);
Additionally, the enumeration TemplatePlaceholder was removed. In order to replace the TemplatePlaceholders with their respective strings, consult the article Email Notifications.
Old Set<TemplateReplacement> replacementSet = new HashSet<TemplateReplacement>(); replacementSet.add(new TemplateReplacement(TemplatePlaceholder.NAME, "John Adams")); replacementSet.add(new TemplateReplacement(TemplatePlaceholder.EMAIL, "john.adams@server.com")); New Set<TemplateReplacement> replacementSet = new HashSet<>(); replacementSet.add(new TemplateReplacement("name", "John Adams")); replacementSet.add(new TemplateReplacement("email", "john.adams@server.com"));
Validating a password
Using the `Client#checkPassword` method, it was previously possible to have the server check the quality of a password. A `PasswordPolicy` object was returned as the return value. This has been replaced by a `PasswordCheckResult` object for this method. Fields not required by this method, which were previously always `null`, have been removed.
Manual setting of transfer errors
When sending email notification was not handled by the Cryptshare server but by the own application, it is possible to tell the Cryptshare server that the sending of emails failed. Using the method Client#updateTransferError(String trackingId, TransferError error) a TransferError can be defined for a specific tracking ID of a transfer. This TransferError has been modified accordingly so that values that can no longer be set cannot be set by setters. Likewise, to set those failed recipients, the recipient list of the TransferError must first be retrieved.
Old TransferError error = new TransferError(); List<String> recipients = new ArrayList<>(); recipients.add("recipient@cryptshare.com"); error.setFailedRecipients(recipients); New TransferError error = new TransferError(); List<String> recipients = new ArrayList<>(); recipients.add("recipient@cryptshare.com"); error.getFailedRecipients().addAll(recipients);
Removed methods
- Transfer#setSessionID
- Transfer#setTransferSize
- Client#manageTransfer
- Client#handleAsynchFileTransferCompleted