/** * Copyright (C) 2013 by Raphael Michel under the MIT license: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software * is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package de.geeksfactory.opacclient.apis; import org.json.JSONException; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; import de.geeksfactory.opacclient.i18n.StringProvider; import de.geeksfactory.opacclient.networking.HttpClientFactory; import de.geeksfactory.opacclient.networking.NotReachableException; import de.geeksfactory.opacclient.objects.Account; import de.geeksfactory.opacclient.objects.AccountData; import de.geeksfactory.opacclient.objects.DetailedItem; import de.geeksfactory.opacclient.objects.Filter; import de.geeksfactory.opacclient.objects.Library; import de.geeksfactory.opacclient.objects.SearchRequestResult; import de.geeksfactory.opacclient.reporting.ReportHandler; import de.geeksfactory.opacclient.searchfields.SearchField; import de.geeksfactory.opacclient.searchfields.SearchQuery; /** * Generic interface for accessing online library catalogues. * * @author Raphael Michel */ public interface OpacApi { /** * Availability of the "prolong all lent items" feature * * Flag to be present in the result of {@link #getSupportFlags()}. */ public static final int SUPPORT_FLAG_ACCOUNT_PROLONG_ALL = 0x0000002; /** * Availability of the "quicklinks" feature * * Flag to be present in the result of {@link #getSupportFlags()}. */ @SuppressWarnings("UnusedDeclaration") // Plus Edition compatibility public static final int SUPPORT_FLAG_QUICKLINKS = 0x0000004; /** * When the results are shown as an endless scrolling list, will reload the page the selected * result is located on if this flag is not present. * * Flag to be present in the result of {@link #getSupportFlags()}. */ public static final int SUPPORT_FLAG_ENDLESS_SCROLLING = 0x0000008; /** * Allow account change on reservation click. * * Flag to be present in the result of {@link #getSupportFlags()}. */ public static final int SUPPORT_FLAG_CHANGE_ACCOUNT = 0x0000010; /** * Asks the user responsibly about reservation fees * * Flag to be present in the result of {@link #getSupportFlags()}. */ public static final int SUPPORT_FLAG_WARN_RESERVATION_FEES = 0x0000020; /** * Asks the user responsibly about prolong fees */ public static final int SUPPORT_FLAG_WARN_PROLONG_FEES = 0x0000040; /** * May be called on application startup and you are free to call it in our {@link #search} * implementation or similar positions. It is commonly used to initialize a session. You MUST * NOT rely on it being called and should check by yourself, whether it was already called (if * your following calls require it to be called before). You SHOULD use this function to * populate the MetaDataSource e.g. with information on your library's branches. * * This function is always called from a background thread, you can use blocking network * operations in it. * * @throws IOException if network connection failed * @throws NotReachableException may throw this if the library couldn't be reached */ public void start() throws IOException; /** * Is called whenever a new API object is created. The difference to start is that you can rely * on it but must not use blocking network functions in it. I use it to initialize my * DefaultHTTPClient and to store the metadata and library objects. * * @param library The library the Api is initialized for * @param httpClientFactory A HttpClientFactory instance that will be used for instantiating * HTTP clients. This factory is pluggable because we want to use * platform-specific code on Android. */ public void init(Library library, HttpClientFactory httpClientFactory); /** * Performs a catalogue search. The given <code>List<SearchQuery></code> contains the * search criteria. See documentation on <code>SearchResult</code> for details. * * This function is always called from a background thread, you can use blocking network * operations in it. See documentation on DetailedItem for details. * * @param query see above * @return List of results and additional information, or result object with the error flag set * to true. * @throws JSONException * @see de.geeksfactory.opacclient.objects.SearchResult */ public SearchRequestResult search(List<SearchQuery> query) throws IOException, OpacErrorException, JSONException; /** * Performs a catalogue search for volumes of an item. The query is given to it from {@link * DetailedItem#getVolumesearch()}. * * This function is always called from a background thread, you can use blocking network * operations in it. See documentation on DetailedItem for details. * * @param query see above * @return List of results and additional information, or result object with the error flag set * to true. * @see de.geeksfactory.opacclient.objects.SearchResult */ public SearchRequestResult volumeSearch(Map<String, String> query) throws IOException, OpacErrorException; /** * If your {@link #search(List)} implementation puts something different from <code>null</code> * into {@link SearchRequestResult#setFilters(List)}, this will be called to apply a filter to * the last search request. * * If your {@link #search(List)} implementation does not set {@link * SearchRequestResult#setFilters(List)}, this wil never be called. Just return * <code>null</code>. * * This function is always called from a background thread, you can use blocking network * operations in it. See documentation on DetailedItem for details. * * @param filter The filter to be applied. * @param option The filters option to be applied. If the <code>option.isApplied()</code> * returns <code>true</code>, the filter is to be removed! * @return List of results and additional information, or result object with the error flag set * to true. * @see de.geeksfactory.opacclient.objects.SearchResult * @see de.geeksfactory.opacclient.objects.Filter * @since 2.0.6 */ @SuppressWarnings({"SameReturnValue", "RedundantThrows", "UnusedDeclaration"}) // Plus Edition compatibility public SearchRequestResult filterResults(Filter filter, Filter.Option option) throws IOException, OpacErrorException; /** * Get result page <code>page</code> of the search performed last with {@link #search}. * * This function is always called from a background thread, you can use blocking network * operations in it. See documentation on DetailedItem for details. * * @param page page number to fetch * @return List of results and additional information, or result object with the error flag set * to true. * @see #search(List) * @see de.geeksfactory.opacclient.objects.SearchResult */ public SearchRequestResult searchGetPage(int page) throws IOException, OpacErrorException, JSONException; /** * Get details for the item with unique ID id. * * This function is always called from a background thread, you can use blocking network * operations in it. * * @param id id of object to fetch * @param homebranch The users "home branch". "Home" library branch. Some library systems * require this information at search request time to determine where book * reservations should be placed. If in doubt, set to <code>null</code>. * @return Media details * @see DetailedItem */ public DetailedItem getResultById(String id, String homebranch) throws IOException, OpacErrorException; /** * Get details for the item at <code>position</code> from last {@link #search} or {@link * #searchGetPage} call. * * We generally prefer {@link #getResultById(String, String)}, so if you implement * <code>getResultById</code> <strong>AND</strong> <em>every</em> search result of your driver * has an id set, you can omit this method (respectively, return null). * * This function is always called from a background thread, you can use blocking network * operations in it. * * @param position position of object in last search * @return Media details * @see DetailedItem */ public DetailedItem getResult(int position) throws IOException, OpacErrorException; /** * Perform a reservation on the item last fetched with <code>getResultById</code> or * <code>getResult</code> for Account <code>acc</code>. (if applicable) * * This function is always called from a background thread, you can use blocking network * operations in it. * * @param item The item to place a reservation for. * @param account Account to be used * @param useraction Identifier for the selection made by the user in <code>selection</code>, if * a selection was made (see {@link ReservationResult#getActionIdentifier()}) * or 0, if no selection was required. If your last method call returned * <code>CONFIRMATION_NEEDED</code>, this is set to * <code>ACTION_CONFIRMATION</code> * if the user positively confirmed the action. * @param selection When the method is called for the first time or if useraction is * <code>ACTION_CONFIRMATION</code>, this parameter is null. If you return * <code>SELECTION</code> in your {@link ReservationResult#getStatus()}, this * method will be called again with the user's selection present in * selection. * @return A <code>ReservationResult</code> object which has to have the status set. */ public ReservationResult reservation(DetailedItem item, Account account, int useraction, String selection) throws IOException; /** * Extend the lending period of the item identified by the given String (see * <code>AccountData</code>) * * This function is always called from a background thread, you can use blocking network * operations in it. * * @param media Media identification * @param account Account to be used * @param useraction Identifier for the selection made by the user in <code>selection</code>, if * a selection was made (see {@link ProlongResult#getActionIdentifier()}) or * 0, if no selection was required. If your last method call returned * <code>CONFIRMATION_NEEDED</code>, this is set to * <code>ACTION_CONFIRMATION</code> * if the user positively confirmed the action. * @param selection When the method is called for the first time or if useraction is * <code>ACTION_CONFIRMATION</code>, this parameter is null. If you return * <code>SELECTION</code> in your {@link ProlongResult#getStatus()}, this * method will be called again with the user's selection present in * selection. * @return A <code>ProlongResult</code> object which has to have the status set. */ public ProlongResult prolong(String media, Account account, int useraction, String selection) throws IOException; /** * Extend the lending period of all lent items. Will only be called if your {@link * #getSupportFlags()} implementation's return value contains the {@link * #SUPPORT_FLAG_ACCOUNT_PROLONG_ALL} flag. If you don't support the feature, just implement a * stub method, like <code>return false;</code> * * This function is always called from a background thread, you can use blocking network * operations in it. * * @return A <code>ProlongAllResult</code> object which has to have the status set. * @see OpacApi#prolong(String, Account, int, String) * @see de.geeksfactory.opacclient.objects.AccountData */ public ProlongAllResult prolongAll(Account account, int useraction, String selection) throws IOException; /** * Cancel a media reservation/order identified by the given String (see AccountData * documentation) (see <code>AccountData</code>) * * This function is always called from a background thread, you can use blocking network * operations in it. * * @param media Media identification * @param account Account to be used * @param useraction Identifier for the selection made by the user in <code>selection</code>, if * a selection was made (see {@link CancelResult#getActionIdentifier()}) or 0, * if no selection was required. If your last method call returned * <code>CONFIRMATION_NEEDED</code>, this is set to * <code>ACTION_CONFIRMATION</code> * if the user positively confirmed the action. * @param selection When the method is called for the first time or if useraction is * <code>ACTION_CONFIRMATION</code>, this parameter is null. If you return * <code>SELECTION</code> in your {@link CancelResult#getStatus()}, this * method will be called again with the user's selection present in * selection. * @return A <code>CancelResult</code> object which has to have the status set. */ public CancelResult cancel(String media, Account account, int useraction, String selection) throws IOException, OpacErrorException; /** * Load account view (borrowed and reserved items, see <code>AccountData</code>) * * This function is always called from a background thread, you can use blocking network * operations in it. * * @param account The account to display * @return Account details * @see de.geeksfactory.opacclient.objects.AccountData */ public AccountData account(Account account) throws IOException, JSONException, OpacErrorException; /** * Check the validity of given account data. This is separate from the {@link #account(Account)} * function because just checking the login can be much faster than retrieving all the account * data. * * This function is always called from a background thread, you can use blocking network * operations in it. * * @param account The account to check * @throws OpacErrorException when the login data is invalid or there's another error message * from the OPAC system */ public void checkAccountData(Account account) throws IOException, JSONException, OpacErrorException; /** * Returns a list of search criteria which are supported by this OPAC and should be visible in * the search activity. Values should be instances of subclasses of the abstract SearchField * class. This is called asynchronously, so you can load webpages to get the search fields, but * you should save them to the metadata afterwards to make it faster. * * @return List of allowed fields * @throws OpacErrorException * @throws JSONException * @see #search */ @SuppressWarnings("RedundantThrows") public List<SearchField> getSearchFields() throws IOException, OpacErrorException, JSONException; /** * Some library systems allow us to share search results. If your library system allows this * natively (to link directly on search results), you can return the corresponding URL with this * function. If your library does not support this at all, return <code>null</code>. If you * library only accepts direkt links when a session is open, get in touch with me * (mail@raphaelmichel.de) to get it integrated in the opacapp.de proxy. * * @param id Media id of the item to be shared * @param title Title of the item to be shared * @return An URL or <strong>null</strong>. */ public String getShareUrl(String id, String title); /** * Return which optional features your API implementation supports. * * @return combination (bitwise OR) of <code>SUPPORT_FLAG_*</code> constants */ public int getSupportFlags(); /** * Sets the StringProvider to use for error messages etc. * * @param stringProvider the StringProvider to use */ public void setStringProvider(StringProvider stringProvider); /** * Get all languages supported by this library. This will be a Set of language codes defined in * ISO-639-1 (two-letter codes in lower case, see * <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">this list</a>). * We don't need to use this function in the app because the API will automatically * fall back if the language set is not supported, but it is used in the MeaningDetector tool to * get search fields for all supported languages. This function may use blocking network * operations and may return null if the API doesn't support different languages. * * @throws IOException */ public Set<String> getSupportedLanguages() throws IOException; /** * Set the language to use. This should be one of the language codes defined in ISO-639-1 * (two-letter codes in lower case, see * <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">this list</a>). * The API should use the default language of the library if this is not called and * should fall back first to English and then to the library's default language if the requested * language is not available. * * @param language the language to use */ public void setLanguage(String language); /** * Sets the report handler to use. * * If an error occurs or other unexpected things happen (such as needing to use a fallback * behaviour that should normally not need to be used) the API might generate a {@link * de.geeksfactory.opacclient.reporting.Report} containing further debugging information. You * can optionally handle these reports (e.g. to send them to the developer) using this methods. * * @param reportHandler the report handler to use */ void setReportHandler(ReportHandler reportHandler); /** * A general exception containing a human-readable error message */ public class OpacErrorException extends Exception { private static final long serialVersionUID = 5834803212488872907L; public OpacErrorException(String message) { super(message); } } /** * The result of a multi-step-supporting method call. * * This is a way of implementing an operating which may need an unregular number of steps with * user interaction. When the user starts the operation, the method is called. It may return * success or error, after which the operation does not continue, but it also may return that it * requires user interaction - either a selection or a confirmation. After the user interacted, * the same method is being called again, but with other parameters. * * @since 2.0.18 */ public abstract class MultiStepResult { /** * Action type identifier for process confirmation */ public static final int ACTION_CONFIRMATION = 2; /** * Action number to use for custom selection type identifiers. */ public static final int ACTION_USER = 100; protected Status status; protected List<Map<String, String>> selection; protected List<String[]> details; protected int actionidentifier; protected String message; /** * Create a new Result object holding the return status of the operation. * * @param status The return status * @see #getStatus() */ public MultiStepResult(Status status) { this.status = status; } /** * Create a new Result object holding the return status of the operation and a message * * @param status The return status * @param message A message * @see #getStatus() */ public MultiStepResult(Status status, String message) { this.status = status; this.message = message; } /** * Get the return status of the operation. Can be <code>OK</code> if the operation was * successful, <code>ERROR</code> if the operation failed, <code>SELECTION_NEEDED</code> if * the user should select one of the options presented in {@link #getSelection()} or * <code>CONFIRMATION_NEEDED</code> if the user should confirm the details returned by * <code>getDetails</code>. . */ public Status getStatus() { return status; } /** * Identifier for the type of user selection if {@link #getStatus()} is * <code>SELECTION_NEEDED</code>. * * @return One of the <code>ACTION_</code> constants or a number above * <code>ACTION_USER</code>. */ public int getActionIdentifier() { return actionidentifier; } /** * Set identifier for the type of user selection if {@link #getStatus()} is * <code>SELECTION_NEEDED</code>. * * @param actionidentifier One of the <code>ACTION_</code> constants or a number above * <code>ACTION_USER</code>. */ public void setActionIdentifier(int actionidentifier) { this.actionidentifier = actionidentifier; } /** * Get values the user should select one of if {@link #getStatus()} is * <code>SELECTION_NEEDED</code>. * * @return A list of maps with the keys 'key' and 'value', where 'key' is what is to be * returned back to reservation() and the value is what is to be displayed to the user. */ public List<Map<String, String>> getSelection() { return selection; } /** * Set values the user should select one of if {@link #getStatus()} is set to * <code>SELECTION_NEEDED</code>. * * @param selection Store with key-value-tuples where the key is what is to be returned back * to reservation() and the value is what is to be displayed to the user. */ public void setSelection(List<Map<String, String>> selection) { this.selection = selection; } /** * If {@link #getStatus()} is <code>CONFIRMATION_NEEDED</code>, this gives you more * information to display to the user. This is a list of of unknown length. Every list entry * is an array of strings that of size one or two (which can vary between the elements of * the list). If the size of such an array A is two, then A[0] contains a description of * A[1], e.g. <code>A = {"Fee", "2 EUR"}</code> or <code>A = {"Pickup location", "Central library"}</code>. * If the size is only one, it is a general message to be shown, e.g. * <code>{"This action will cost 2 EUR."}</code>. * * @return A list of String[] entries, as described above. */ public List<String[]> getDetails() { return details; } /** * Set details the user should confirm if {@link #getStatus()} is * <code>CONFIRMATION_NEEDED</code>. * * @param details List containing reservation details. See {@link #getDetails()} for what this means. */ public void setDetails(List<String[]> details) { this.details = details; } /** * @return A optional message, e.g. to explain an error status code */ public String getMessage() { return message; } /** * Set an optional message, e.g. to explain an error status code */ public void setMessage(String message) { this.message = message; } public enum Status { /** * Everything went well */ OK, /** * This is not supported in this API implementation */ UNSUPPORTED, /** * The user's web browser should be opened */ EXTERNAL, /** * An error occured */ ERROR, /** * The user has to make a selection */ SELECTION_NEEDED, /** * The user has to confirm the prolonging */ CONFIRMATION_NEEDED, /** * We need the user's emaila ddress */ EMAIL_NEEDED } @Override public String toString() { return "MultiStepResult{" + "status=" + status + ", selection=" + selection + ", details=" + details + ", actionidentifier=" + actionidentifier + ", message='" + message + '\'' + '}'; } } /** * The result of a {@link OpacApi#reservation(DetailedItem, Account, int, String)} call */ public class ReservationResult extends MultiStepResult { /** * Action type identifier for library branch selection */ public static final int ACTION_BRANCH = 1; public ReservationResult(Status status) { super(status); } public ReservationResult(Status status, String message) { super(status, message); } } /** * The result of a {@link OpacApi#prolong(String, Account, int, String)} call */ public class ProlongResult extends MultiStepResult { public ProlongResult(Status status) { super(status); } public ProlongResult(Status status, String message) { super(status, message); } } /** * The result of a {@link OpacApi#prolongAll(Account, int, String)} call */ public class ProlongAllResult extends MultiStepResult { public static final String KEY_LINE_TITLE = "title"; public static final String KEY_LINE_AUTHOR = "author"; public static final String KEY_LINE_NR = "nr"; public static final String KEY_LINE_OLD_RETURNDATE = "olddate"; public static final String KEY_LINE_NEW_RETURNDATE = "newdate"; public static final String KEY_LINE_MESSAGE = "message"; protected List<Map<String, String>> results; /** * @param results A list of ContentValues containing the success values for all the single * items we (tried to) renew. */ public ProlongAllResult(Status status, List<Map<String, String>> results) { super(status); this.results = results; } public ProlongAllResult(Status status, String message) { super(status, message); } public ProlongAllResult(Status status) { super(status); } public List<Map<String, String>> getResults() { return results; } } /** * The result of a {@link OpacApi#cancel(String, Account, int, String)} call */ public class CancelResult extends MultiStepResult { public CancelResult(Status status) { super(status); } public CancelResult(Status status, String message) { super(status, message); } } }