/** * 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.io.File; import java.io.FileNotFoundException; import org.sana.android.db.DBUtils; import org.sana.android.db.DatabaseManager; import org.sana.android.db.DatabaseOpenHelper; import org.sana.android.db.TableHelper; import org.sana.android.db.impl.ConceptsHelper; import org.sana.android.db.impl.EncounterTasksHelper; import org.sana.android.db.impl.EncountersHelper; import org.sana.android.db.impl.EventsHelper; import org.sana.android.db.impl.InstructionsHelper; import org.sana.android.db.impl.NotificationsHelper; import org.sana.android.db.impl.ObservationsHelper; import org.sana.android.db.impl.ObserversHelper; import org.sana.android.db.impl.ProceduresHelper; import org.sana.android.db.impl.SubjectsHelper; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.text.TextUtils; import android.util.Log; /** * Abstract implementation of of {@link android.content.ContentProvider} for * classes extending {@link org.sana.api.IModel} each of which maps to a table within * the database. This implementation uses the * {@link org.sana.android.content.Uris Uris} for mapping the * {@link android.net.Uri Uri's} to each table and extensions of the * {@link org.sana.android.db.TableHelper} class to handle interactions for * each table. Extending classes should only need to implement the {@link #onCreate()} * method which provide the database name and version. * * @author Sana Development * */ public abstract class ModelContentProvider extends ContentProvider { public static final String TAG = ModelContentProvider.class.getSimpleName(); public static final String AUTHORITY = "org.sana.provider"; protected static final String DATABASE = "models.db"; protected DatabaseOpenHelper mOpener; protected DatabaseManager mManager; // match types public static final int ITEMS = 0; public static final int ITEM_ID = 1; static final ModelMatcher mMatcher = ModelMatcher.getInstance(); protected String getTable(Uri uri){ return getTableHelper(uri).getTable(); } protected TableHelper<?> getTableHelper(Uri uri){ int match = Uris.getContentDescriptor(uri); switch(match){ case(Uris.CONCEPT): return ConceptsHelper.getInstance(); case(Uris.ENCOUNTER): return EncountersHelper.getInstance(); case(Uris.EVENT): return EventsHelper.getInstance(); case(Uris.INSTRUCTION): return InstructionsHelper.getInstance(); case(Uris.NOTIFICATION): return NotificationsHelper.getInstance(); case(Uris.OBSERVATION): return ObservationsHelper.getInstance(); case(Uris.OBSERVER): return ObserversHelper.getInstance(); case(Uris.PROCEDURE): return ProceduresHelper.getInstance(); case(Uris.SUBJECT): return SubjectsHelper.getInstance(); case(Uris.ENCOUNTER_TASK): return EncounterTasksHelper.getInstance(); default: throw new IllegalArgumentException("Invalid uri in " +"getTableHelper(): " + uri.toString()); } } /* (non-Javadoc) * @see android.content.ContentProvider#delete(android.net.Uri, java.lang.String, java.lang.String[]) */ @Override public synchronized int delete(Uri uri, String selection, String[] selectionArgs) { Log.d(TAG, "delete() uri=" + uri + ", selection= " + selection + ", selectionArgs=" + ((selectionArgs != null)?TextUtils.join(",", selectionArgs):"null") + " );"); String whereClause = DBUtils.getWhereClause(uri, Uris.getDescriptor(uri), selection); switch(Uris.getTypeDescriptor(uri)){ case(Uris.ITEM_ID): selection = DBUtils.getWhereClauseWithID(uri, selection); break; case(Uris.ITEM_UUID): selection = DBUtils.getWhereClauseWithUUID(uri, selection); default: } TableHelper<?> helper = getTableHelper(uri); String table = helper.getTable(); SQLiteDatabase db = DatabaseManager.getInstance().openDatabase(); int count = db.delete(table, selection, selectionArgs); DatabaseManager.getInstance().closeDatabase(); getContext().getContentResolver().notifyChange(uri, null); return count; } /* (non-Javadoc) * @see android.content.ContentProvider#getType(android.net.Uri) */ @Override public String getType(Uri uri) { return Uris.getType(uri); } /* (non-Javadoc) * @see android.content.ContentProvider#insert(android.net.Uri, android.content.ContentValues) */ @Override public synchronized Uri insert(Uri uri, ContentValues values) { Log.d(TAG, "insert(" + uri.toString() +", N = " + String.valueOf((values == null)?0:values.size()) + " values.)"); TableHelper<?> helper = getTableHelper(uri); // set default insert values and execute values = helper.onInsert(values); String table = helper.getTable(); SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();//mOpener.getWritableDatabase(); long id = db.insert(table, null, values); DatabaseManager.getInstance().closeDatabase(); Uri result = ContentUris.withAppendedId(uri, id); getContext().getContentResolver().notifyChange(uri, null); Log.d(TAG, "insert(): Successfully inserted => " + result); return result; } /* (non-Javadoc) * @see android.content.ContentProvider#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) */ @Override public synchronized Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.d(TAG, ".query(" + uri.toString() +");"); TableHelper<?> helper = getTableHelper(uri); // set query and execute sortOrder = (TextUtils.isEmpty(sortOrder))? helper.onSort(uri): sortOrder; switch(Uris.getTypeDescriptor(uri)){ case(Uris.ITEM_ID): selection = DBUtils.getWhereClauseWithID(uri, selection); break; case(Uris.ITEM_UUID): selection = DBUtils.getWhereClauseWithUUID(uri, selection); default: } Log.d(TAG, ".query(.) selection = " + selection); String uriQS = DBUtils.convertUriQueryToSelect(uri); Log.d(TAG, ".query(.) uri qs = " + selection); if(!TextUtils.isEmpty(uriQS)){ selection = String.format("%s %s", selection, uriQS); Log.d(TAG, ".query(.) selection --> " + selection); } SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(helper.getTable()); SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();//mOpener.getReadableDatabase(); //Cursor cursor = helper.onQuery(db, projection, selection, selectionArgs, sortOrder); Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); Log.d(TAG, ".query(" + uri.toString() +") count = " + ((cursor!=null)?cursor.getCount():0)); return cursor; } /* (non-Javadoc) * @see android.content.ContentProvider#update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) */ @Override public synchronized int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d(TAG, ".update(" + uri.toString() +");");//mOpener.getWritableDatabase(); // set any default update values TableHelper<?> helper = getTableHelper(uri); values = helper.onUpdate(uri, values); String table = helper.getTable(); // set selection and execute switch(Uris.getTypeDescriptor(uri)){ case(Uris.ITEM_ID): selection = DBUtils.getWhereClauseWithID(uri, selection); break; case(Uris.ITEM_UUID): selection = DBUtils.getWhereClauseWithUUID(uri, selection); default: } SQLiteDatabase db = DatabaseManager.getInstance().openDatabase(); int result = db.update(table, values, selection, selectionArgs); DatabaseManager.getInstance().closeDatabase(); getContext().getContentResolver().notifyChange(uri, null); return result; } /* * (non-Javadoc) * @see android.content.ContentProvider#openFile(android.net.Uri, java.lang.String) */ @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { Log.i(TAG, "openFile()" + uri); Log.d(TAG,"...uri: " + uri); Log.d(TAG,"...mode: " + mode); String ext = getTableHelper(uri).getFileExtension(); int match = Uris.getContentDescriptor(uri); switch(match){ case(Uris.OBSERVATION): case(Uris.SUBJECT): break; default: throw new FileNotFoundException("Unsupported content type. No files."); } TableHelper<?> helper = getTableHelper(uri); String column = helper.getFileColumn(); Cursor c = query(uri, new String[]{ column }, null, null, null); String path = null; if (c != null) { try{ if(c.moveToFirst()){ // Should never get more than one back if(c.getCount() > 1) throw new IllegalArgumentException( "Vaild for single row only"); // get file and open path = c.getString(0); Log.d(TAG, "...opening file path: " + path); } else { throw new IllegalArgumentException("Invalid Uri: " + uri); } } finally { c.close(); } } File fopen; int modeBits = modeToMode(mode); // Create file if (TextUtils.isEmpty(path)){ Log.d(TAG,"...path was empty."); if(modeBits == ParcelFileDescriptor.MODE_READ_ONLY) throw new IllegalArgumentException("Read only open on empty File path " +"in column '" + column + "' for uri: " + uri); // Open in read write with no file name long id = ContentUris.parseId(uri); File dir = getContext().getExternalFilesDir(helper.getTable()); boolean created = dir.mkdirs(); Log.d(TAG,"...created parent dirs: " + created); //File dir = new File(fDir,helper.getTable()); dir.mkdirs(); fopen = new File(dir,String.format("%s.%s", id,ext)); // Update file column with absolute path ContentValues values = new ContentValues(); values.put(column, fopen.getAbsolutePath()); int updated = getContext().getContentResolver().update(uri, values, null,null); Log.d(TAG, "updated: " + updated + ", file: " + fopen.getAbsolutePath()); } else { fopen = new File(path); } Log.d(TAG,"...opening file: " + fopen.getAbsolutePath()); Log.d(TAG,"...opening in mode: " + mode); return ParcelFileDescriptor.open(fopen, modeBits); } protected final int modeToMode(String mode){ int modeBits; if ("r".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_ONLY; } else if ("w".equals(mode) || "wt".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE; } else if ("wa".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_APPEND; } else if ("rw".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE; } else if ("rwt".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE; } else { throw new IllegalArgumentException("Bad mode: " + mode); } return modeBits; } }