/** * Copyright (c) 2013, Sana * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the Sana nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL Sana BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.sana.android.content; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import org.sana.android.provider.Concepts; import org.sana.android.provider.EncounterTasks; import org.sana.android.provider.Encounters; import org.sana.android.provider.Events; import org.sana.android.provider.Instructions; import org.sana.android.provider.Models; import org.sana.android.provider.Notifications; import org.sana.android.provider.ObservationTasks; import org.sana.android.provider.Observations; import org.sana.android.provider.Observers; import org.sana.android.provider.Procedures; import org.sana.android.provider.Subjects; import org.sana.util.UUIDUtil; import android.content.Context; import android.content.SharedPreferences; import android.content.UriMatcher; import android.net.Uri; import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; /** * a container of Uri descriptor values and related constants. The static * methods of this class are primarily intended for consistent, application-wide * Uri matching. * * @author Sana Development * */ public final class Uris { private static final String SCHEME_CONTENT = "content"; public static final int NO_MATCH = -1; public static final int MATCH_ALL = 0; public static final int MATCH_TYPE = 1; public static final int MATCH_CONTENT = 2; public static final int MATCH_PACKAGE = 4; public static final int ITEMS = 1; public static final int ITEM_ID = 2; public static final int ITEM_UUID = 4; public static final int ITEM_RELATED = 8; public static final int ITEM_FILE = 16; public static final int TYPE_WIDTH = 8; public static final int TYPE_SHIFT = 0; private static final int TYPE_MASK = (1 << TYPE_WIDTH) -1; public static final int CONTENT_WIDTH = 16; public static final int CONTENT_SHIFT = TYPE_WIDTH; private static final int CONTENT_MASK = ((1 << CONTENT_WIDTH) - 1) << CONTENT_SHIFT; public static final int PACKAGE_WIDTH = 4; public static final int PACKAGE_SHIFT = CONTENT_WIDTH + CONTENT_SHIFT; private static final int PACKAGE_MASK = ((1 << PACKAGE_WIDTH) - 1) << PACKAGE_SHIFT; public static final int DESCRIPTOR_WIDTH = PACKAGE_WIDTH + CONTENT_WIDTH + TYPE_WIDTH; public static final int NULL = UriMatcher.NO_MATCH; //-------------------------------------------------------------------------- // Application, i.e. Package codes //-------------------------------------------------------------------------- public static final String PACKAGE_AUTHORITY = "org.sana"; public static final int PACKAGE_DIR = 0x000000001; /* public static final Uri INTENT_URI = buildContentUri(PACKAGE_AUTHORITY, "intent"); /** Uri which identifies the application settings * public static final Uri SETTINGS_URI = buildContentUri(PACKAGE_AUTHORITY, "settings"); /** Uri which identifies session activity * public static final Uri SESSION_URI =buildContentUri(PACKAGE_AUTHORITY, "session"); public static final int PACKAGE = 1; public static final int SESSION = 2 << (DESCRIPTOR_WIDTH - PACKAGE_SHIFT); public static final int SETTINGS = 4 << (DESCRIPTOR_WIDTH - PACKAGE_SHIFT); public static final int PACKAGE_DIR = PACKAGE | ITEMS; public static final int SESSION_DIR = SESSION | ITEMS; public static final int SETTINGS_DIR = SETTINGS | ITEMS; public static final int SESSION_ITEM = SESSION | ITEM_ID; public static final int SETTINGS_ITEM = SETTINGS | ITEM_ID; public static final int SESSION_UUID = SESSION | ITEM_UUID; public static final int SETTINGS_UUID = SETTINGS | ITEM_UUID; static interface Sessions{ static final String CONTENT_TYPE = "vnd.android.cursor.dir/org.sana.session"; static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/org.sana.session"; } static interface Settings{ static final String CONTENT_TYPE = "vnd.android.cursor.dir/org.sana.setting"; static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/org.sana.setting"; } */ //-------------------------------------------------------------------------- // Model codes //-------------------------------------------------------------------------- public static final int CONCEPT = 1 << CONTENT_SHIFT; public static final int ENCOUNTER = 2 << CONTENT_SHIFT; public static final int EVENT = 4 << CONTENT_SHIFT; public static final int INSTRUCTION = 8 << CONTENT_SHIFT; public static final int NOTIFICATION = 6 << CONTENT_SHIFT; public static final int OBSERVATION = 32 << CONTENT_SHIFT; public static final int OBSERVER = 64 << CONTENT_SHIFT; public static final int PROCEDURE = 128 << CONTENT_SHIFT; public static final int RELATIONSHIP = 256 << CONTENT_SHIFT; public static final int SUBJECT = 512 << CONTENT_SHIFT; public static final int ENCOUNTER_TASK = 1024 << CONTENT_SHIFT; public static final int OBSERVATION_TASK = 2048 << CONTENT_SHIFT; // dir match codes OBJECT | ITEMS public static final int CONCEPT_DIR = CONCEPT | ITEMS; public static final int ENCOUNTER_DIR = ENCOUNTER | ITEMS; public static final int EVENT_DIR = EVENT | ITEMS; public static final int INSTRUCTION_DIR = INSTRUCTION | ITEMS; public static final int NOTIFICATION_DIR = NOTIFICATION | ITEMS; public static final int OBSERVATION_DIR = OBSERVATION | ITEMS; public static final int OBSERVER_DIR = OBSERVER | ITEMS; public static final int PROCEDURE_DIR = PROCEDURE | ITEMS; public static final int RELATIONSHIP_DIR = RELATIONSHIP | ITEMS; public static final int SUBJECT_DIR = SUBJECT | ITEMS; public static final int ENCOUNTER_TASK_DIR = ENCOUNTER_TASK | ITEMS; public static final int OBSERVATION_TASK_DIR = OBSERVATION_TASK | ITEMS; // item match codes OBJECT | ITEM_ID public static final int CONCEPT_ITEM = CONCEPT | ITEM_ID; public static final int ENCOUNTER_ITEM = ENCOUNTER | ITEM_ID; public static final int EVENT_ITEM = EVENT | ITEM_ID; public static final int INSTRUCTION_ITEM = INSTRUCTION | ITEM_ID; public static final int NOTIFICATION_ITEM = NOTIFICATION | ITEM_ID; public static final int OBSERVATION_ITEM = OBSERVATION | ITEM_ID; public static final int OBSERVER_ITEM = OBSERVER | ITEM_ID; public static final int PROCEDURE_ITEM = PROCEDURE | ITEM_ID; public static final int RELATIONSHIP_ITEM = RELATIONSHIP | ITEM_ID; public static final int SUBJECT_ITEM = SUBJECT | ITEM_ID; public static final int ENCOUNTER_TASK_ITEM = ENCOUNTER_TASK | ITEM_ID; public static final int OBSERVATION_TASK_ITEM = OBSERVATION_TASK | ITEM_ID; // item match codes OBJECT | ITEM_UUID public static final int CONCEPT_UUID = CONCEPT | ITEM_UUID; public static final int ENCOUNTER_UUID = ENCOUNTER | ITEM_UUID; public static final int EVENT_UUID = EVENT | ITEM_UUID; public static final int INSTRUCTION_UUID = INSTRUCTION | ITEM_UUID; public static final int NOTIFICATION_UUID = NOTIFICATION | ITEM_UUID; public static final int OBSERVATION_UUID = OBSERVATION | ITEM_UUID; public static final int OBSERVER_UUID = OBSERVER | ITEM_UUID; public static final int PROCEDURE_UUID = PROCEDURE | ITEM_UUID; public static final int RELATIONSHIP_UUID = RELATIONSHIP | ITEM_UUID; public static final int SUBJECT_UUID = SUBJECT | ITEM_UUID; public static final int ENCOUNTER_TASK_UUID = ENCOUNTER_TASK | ITEM_UUID; public static final int OBSERVATION_TASK_UUID = OBSERVATION_TASK | ITEM_UUID; // Matcher for mapping the Uri to code mappings private static final UriMatcher mMatcher = new UriMatcher(UriMatcher.NO_MATCH); static{ /* // admin mappings mMatcher.addURI(PACKAGE_AUTHORITY, "/", PACKAGE_DIR); mMatcher.addURI(PACKAGE_AUTHORITY, "session/", SESSION_DIR); mMatcher.addURI(PACKAGE_AUTHORITY, "session/#", SESSION_ITEM); mMatcher.addURI(PACKAGE_AUTHORITY, "session/*", SESSION_UUID); mMatcher.addURI(PACKAGE_AUTHORITY, "settings/", SETTINGS_DIR); mMatcher.addURI(PACKAGE_AUTHORITY, "settings/#", SETTINGS_ITEM); mMatcher.addURI(PACKAGE_AUTHORITY, "settings/*", SETTINGS_UUID); */ mMatcher.addURI(Models.AUTHORITY, "/", PACKAGE_DIR); mMatcher.addURI(Models.AUTHORITY, "core/concept/", CONCEPT_DIR); mMatcher.addURI(Models.AUTHORITY, "core/concept/#", CONCEPT_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/concept/*", CONCEPT_UUID); mMatcher.addURI(Models.AUTHORITY, "core/encounter/", ENCOUNTER_DIR); mMatcher.addURI(Models.AUTHORITY, "core/encounter/#", ENCOUNTER_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/encounter/*", ENCOUNTER_UUID); mMatcher.addURI(Models.AUTHORITY, "core/event/", EVENT_DIR); mMatcher.addURI(Models.AUTHORITY, "core/event/#", EVENT_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/event/*", EVENT_UUID); mMatcher.addURI(Models.AUTHORITY, "core/instruction/", INSTRUCTION_DIR); mMatcher.addURI(Models.AUTHORITY, "core/instruction/#", INSTRUCTION_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/instruction/*", INSTRUCTION_UUID); mMatcher.addURI(Models.AUTHORITY, "core/notification/", NOTIFICATION_DIR); mMatcher.addURI(Models.AUTHORITY, "core/notification/#", NOTIFICATION_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/notification/*", NOTIFICATION_UUID); mMatcher.addURI(Models.AUTHORITY, "core/observation/", OBSERVATION_DIR); mMatcher.addURI(Models.AUTHORITY, "core/observation/#", OBSERVATION_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/observation/*", OBSERVATION_UUID); mMatcher.addURI(Models.AUTHORITY, "core/observer/", OBSERVER_DIR); mMatcher.addURI(Models.AUTHORITY, "core/observer/#", OBSERVER_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/observer/*", OBSERVER_UUID); mMatcher.addURI(Models.AUTHORITY, "core/procedure/", PROCEDURE_DIR); mMatcher.addURI(Models.AUTHORITY, "core/procedure/#", PROCEDURE_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/procedure/*", PROCEDURE_UUID); mMatcher.addURI(Models.AUTHORITY, "core/subject/", SUBJECT_DIR); mMatcher.addURI(Models.AUTHORITY, "core/subject/#", SUBJECT_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/subject/*", SUBJECT_UUID); mMatcher.addURI(Models.AUTHORITY, "core/patient/", SUBJECT_DIR); mMatcher.addURI(Models.AUTHORITY, "core/patient/#", SUBJECT_ITEM); mMatcher.addURI(Models.AUTHORITY, "core/patient/*", SUBJECT_UUID); mMatcher.addURI(Models.AUTHORITY, "tasks/encounter/", ENCOUNTER_TASK_DIR); mMatcher.addURI(Models.AUTHORITY, "tasks/encounter/#", ENCOUNTER_TASK_ITEM); mMatcher.addURI(Models.AUTHORITY, "tasks/encounter/*", ENCOUNTER_TASK_UUID); mMatcher.addURI(Models.AUTHORITY, "tasks/observation/", OBSERVATION_TASK_DIR); mMatcher.addURI(Models.AUTHORITY, "tasks/observation/#", OBSERVATION_TASK_ITEM); mMatcher.addURI(Models.AUTHORITY, "tasks/observation/*", OBSERVATION_TASK_UUID); } /** * Returns an int value describing the content and type represented by the * Uri * * @param uri The Uri to check. * @return a value greater than 0 if the Uri was recognized or the value of * {@link android.content.UriMatcher#NO_MATCH UriMatcher.NO_MATCH}. * @throws IllegalArgumentException if the descriptor can not be determined * or the UUID provided as a path segment is invalid. */ public static int getDescriptor(Uri uri){ int result = mMatcher.match((uri !=null)? uri: Uri.EMPTY); if(result > -1){ if(((result & TYPE_MASK) >> TYPE_SHIFT) == Uris.ITEM_UUID){ String uuid = uri.getLastPathSegment(); if(!UUIDUtil.isValid(uuid)) throw new IllegalArgumentException("Invalid uuid format"); } } return result; } /** * Returns the content object class descriptor for the Uri. * * @param uri * @return * @throws IllegalArgumentException if the descriptor can not be determined. */ public static int getContentDescriptor(Uri uri){ int d = getDescriptor(uri) & CONTENT_MASK; return (d > -1)? d: UriMatcher.NO_MATCH; } /** * The content type descriptor. If matched, it will return one of * ITEMS, ITEMS_ID, or ITEM_UUID * * @param uri * @return * @throws IllegalArgumentException if the descriptor can not be determined. */ public static int getTypeDescriptor(Uri uri){ int d = getDescriptor(uri) & TYPE_MASK; return (d > -1)? d: UriMatcher.NO_MATCH; } /** * Builds a hierarchical Uri. * * @param scheme * @param authority * @param path * @return */ public static Uri buildUri(String scheme, String authority, String path){ Uri uri = Uri.parse(scheme + "://"); Uri.Builder builder = uri.buildUpon(); builder.scheme(scheme).authority(authority); for(String s:path.split("/")){ if(!TextUtils.isEmpty(s)){ builder.appendPath(path); } } return builder.build(); } /** * Builds a hierarchical content style Uri. * * @param authority The authority String * @param path * @return */ public static Uri buildContentUri(String authority, String path){ return buildUri(SCHEME_CONTENT,authority, path ); } /** * Parses the last path segment. This method performs no validation on the * format. * * @param uri * @return */ public static String parseUUID(Uri uri){ return uri.getLastPathSegment(); } /** * Appends a uuid String as the last path segment. This method performs no * validation on the format. * * @param uri * @param uuid * @return */ public static Uri withAppendedUuid(Uri uri, String uuid){ // verify that the Uri is valid if(Uris.isEmpty(uri)) throw new NullPointerException("Empty uri. Can not append UUID"); Uri result = Uri.parse(uri.toString() + "/" + uuid); return result; } /** * Will return the mime type represented by the Uri. For item or uuid * matches-i.e. path matching {@literal value/#} and {@literal value/*} * respectively, it will return a type beginning with * "vnd.android.cursor.item", otherwise, a type beginning with * "vnd.android.cursor.dir" will be returned. * * @param uri * @return The mime type for the Uri * @throws IllegalArgumentException if the mime type can not be determined. */ public static String getType(Uri uri) { switch (getDescriptor(uri)){ case CONCEPT_DIR: return Concepts.CONTENT_TYPE; case CONCEPT_UUID: case CONCEPT_ITEM: return Concepts.CONTENT_ITEM_TYPE; case ENCOUNTER_DIR: return Encounters.CONTENT_TYPE; case ENCOUNTER_UUID: case ENCOUNTER_ITEM: return Encounters.CONTENT_ITEM_TYPE; case EVENT_DIR: return Events.CONTENT_TYPE; case EVENT_UUID: case EVENT_ITEM: return Events.CONTENT_ITEM_TYPE; case INSTRUCTION_DIR: return Instructions.CONTENT_TYPE; case INSTRUCTION_UUID: case INSTRUCTION_ITEM: return Instructions.CONTENT_ITEM_TYPE; case NOTIFICATION_DIR: return Notifications.CONTENT_TYPE; case NOTIFICATION_UUID: case NOTIFICATION_ITEM: return Notifications.CONTENT_ITEM_TYPE; case OBSERVATION_DIR: return Observations.CONTENT_TYPE; case OBSERVATION_UUID: case OBSERVATION_ITEM: return Observations.CONTENT_ITEM_TYPE; case OBSERVER_DIR: return Observers.CONTENT_TYPE; case OBSERVER_UUID: case OBSERVER_ITEM: return Observers.CONTENT_ITEM_TYPE; case PROCEDURE_DIR: return Procedures.CONTENT_TYPE; case PROCEDURE_UUID: case PROCEDURE_ITEM: return Procedures.CONTENT_ITEM_TYPE; case SUBJECT_DIR: return Subjects.CONTENT_TYPE; case SUBJECT_UUID: case SUBJECT_ITEM: return Subjects.CONTENT_ITEM_TYPE; case ENCOUNTER_TASK_DIR: return EncounterTasks.CONTENT_TYPE; case ENCOUNTER_TASK_UUID: case ENCOUNTER_TASK_ITEM: return EncounterTasks.CONTENT_ITEM_TYPE; case OBSERVATION_TASK_DIR: return ObservationTasks.CONTENT_TYPE; case OBSERVATION_TASK_UUID: case OBSERVATION_TASK_ITEM: return ObservationTasks.CONTENT_ITEM_TYPE; case PACKAGE_DIR: return "application/vnd.android.package-archive"; default: throw new IllegalArgumentException("Invalid uri. No match"); } } /** * Returns whether a Uri is a content directory type. The mime types for * directory uris should typically start with ""vnd.android.cursor.dir". * * @param uri the Uri to check * @return true if the Uri may refer to multiple objects * @throws IllegalArgumentException if the type can not be determined. */ public static boolean isDirType(Uri uri){ return (getContentDescriptor(uri) & TYPE_MASK) == ITEMS; } /** * Returns true if the Uri refers to a single item type. The mime types for * single item uris should should start with "vnd.android.cursor.item". * * @param uri the Uri to check * @return true if the Uri may refer to multiple objects * @throws IllegalArgumentException if the type can not be determined. */ public static boolean isItemType(Uri uri){ int val = getContentDescriptor(uri) & TYPE_MASK; return (val == ITEM_ID) || (val == ITEM_UUID); } public static boolean isPackage(Uri uri){ return (uri.getScheme().equals("package")); } /** * Returns true if the Uri is null or equal to Uri.EMPTY * @param uri * @return */ public static boolean isEmpty(Uri uri){ return (uri != null && !uri.equals(Uri.EMPTY))? false: true; } public static boolean isTask(Uri uri){ int d = getTypeDescriptor(uri); if(d == UriMatcher.NO_MATCH) return false; return ((d == ENCOUNTER_TASK) || (d == OBSERVATION_TASK))? true: false; } public static final boolean filterEquals(Uri from, Uri to, int flags){ boolean result = false; if(flags == MATCH_ALL){ result = (getDescriptor(from) == getDescriptor(to)); } else { // TODO implement this } return result; } public static final boolean filterEquals(Uri from, Uri to){ return filterEquals(from,to, MATCH_ALL); } /** * Copy constructor utility. * * @param uri The uri to copy. * @return A copy of the input parameter or Uri.EMPTY; */ public static Uri copyInstance(Uri uri){ return (uri == null)? Uri.EMPTY: Uri.parse(uri.toString()); } public static Uri iriToUri(Uri iri){ Uri result = Uri.EMPTY; return result; } /** * Utility function to return a Uri whose path can be used for web based * services. POST and PUT methods will remove the trailing ID or UUID if * present. * @param iri * @param method * @return */ public static Uri iriToUri(Uri iri, String method){ Uri result = Uri.EMPTY; return result; } /** * Converts Android "content" style resource identifiers to URIs to use with the * new MDS REST API. Only works for those objects whose path components in the new * REST API are consistent with MDS. * * @param uri The internal resource identifier to convert. * @param scheme The scheme to use for the conversion * @param host The mds host * @param port The mds port to talk to. * @param rootPath Additional * @return * @throws MalformedURLException * @throws URISyntaxException */ public static URI iriToURI(Uri uri, String scheme, String host, int port, String rootPath) throws MalformedURLException, URISyntaxException { String path = String.format("%s%s", rootPath, uri.getPath()); if(!path.endsWith("/")) path = path + "/"; String query = uri.getEncodedQuery(); URL url = null; if(!TextUtils.isEmpty(query)){ path = String.format("%s%s?%s", path,query); url = new URL(scheme, host, port, path); } else {String.format("%s%s", rootPath, uri.getPath()); url = new URL(scheme, host, port, path); } URI u = url.toURI(); return u; } }