Java tutorial
/* * Copyright 2006-2013, Rathravane LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.rathravane.drumlin.examples.accounts; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.LinkedList; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.rathravane.drumlin.app.accounts.DrumlinAccountPersistence; import com.rathravane.drumlin.app.accounts.DrumlinAccountsException; import com.rathravane.drumlin.app.accounts.DrumlinApiKey; import com.rathravane.till.logging.rrLogSetup; import com.rathravane.till.store.rrJsonObjectFile; public class exampleAcctPersistence implements DrumlinAccountPersistence<exampleUser, exampleAccount> { public static void initialize(File file, String passwd) throws IOException { rrJsonObjectFile.initialize(file, 1024); final rrJsonObjectFile store = new rrJsonObjectFile(file, true, passwd); // the first record in the file is an index container final long indexLocation = store.indexToAddress(0); final JSONObject index = new JSONObject(); for (String indexName : new String[] { kIndex_UserList, kIndex_AcctList, kIndex_ApiKeys, kIndex_RequestTags }) { index.put(indexName, new JSONObject()); } store.overwrite(indexLocation, index); store.close(); } public exampleAcctPersistence(rrJsonObjectFile store) throws IOException { fStore = store; fIndex = store.read(store.indexToAddress(0)); // the index remains in memory, with changes written back to the file } @Override public String getAppSignature() { return "The app signature is a constant string that introduces app-specific data into tag creation and password hashing."; } @Override public void close() { try { fStore.close(); } catch (IOException e) { log.warn("Error closing store. " + e.getMessage(), e); } } @Override public boolean userExists(String uid) throws DrumlinAccountsException { return (loadUser(uid) != null); } @Override public exampleUser createUser(String acctId, String userId, String userEmail) throws DrumlinAccountsException { final JSONObject user = new JSONObject(); user.put(kField_Type, kValue_User); user.put(kField_AcctId, acctId); user.put(kField_Enabled, false); final long loc = writeAndIndex(user, kIndex_UserList, userId); return new exampleUser(loc, user); } @Override public void deleteUser(String userId) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); if (ri != null) { removeFromIndex(kIndex_UserList, userId); try { fStore.delete(ri.location); } catch (IOException e) { throw new DrumlinAccountsException(e); } } } @Override public exampleUser loadUser(String id) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, id, kValue_User); if (ri != null) { return new exampleUser(ri.location, ri.record); } else { return null; } } @Override public String getAccountIdForUser(String id) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, id, kValue_User); return ri != null ? ri.record.optString(kField_AcctId, null) : null; } @Override public boolean isUserEnabled(String id) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, id, kValue_User); return ri != null && ri.record.optBoolean(kField_Enabled, false); } @Override public void enableUser(String id, boolean enabled) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, id, kValue_User); if (ri != null) { ri.record.put(kField_Enabled, enabled); ri.write(); } } @Override public void storePasswordInfo(String id, String salt, String hashedPassword) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, id, kValue_User); if (ri != null) { ri.record.put(kField_Salt, salt); ri.record.put(kField_HashedPassword, hashedPassword); ri.write(); } } @Override public String[] getPasswordInfo(String id) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, id, kValue_User); if (ri != null) { return new String[] { ri.record.optString(kField_Salt, ""), ri.record.optString(kField_HashedPassword, ""), }; } return new String[] { "", "" }; } @Override public boolean accountExists(String id) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_AcctList, id, kValue_Acct); return ri != null; } @Override public exampleAccount createAccount(String id) throws DrumlinAccountsException { final JSONObject acct = new JSONObject(); acct.put(kField_Type, kValue_Acct); acct.put(kField_Enabled, false); final long loc = writeAndIndex(acct, kIndex_AcctList, id); return new exampleAccount(loc, acct); } @Override public exampleAccount loadAccount(String id) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_AcctList, id, kValue_Acct); if (ri != null) { return new exampleAccount(ri.location, ri.record); } else { return null; } } @Override public boolean isAccountEnabled(String acctId) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_AcctList, acctId, kValue_Acct); return ri != null && ri.record.optBoolean(kField_Enabled, false); } @Override public void enableAccount(String id, boolean enabled) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_AcctList, id, kValue_Acct); if (ri != null) { ri.record.put(kField_Enabled, enabled); ri.write(); } } @Override public void storeRequestTag(String userId, String tagType, String tag, long expiresAtEpochTime) throws DrumlinAccountsException { final JSONObject o = new JSONObject(); o.put(kField_UserId, userId); o.put(kField_Tag, tag); o.put(kField_Expires, expiresAtEpochTime); o.put(kField_Type, tagType); writeAndIndex(o, kIndex_RequestTags, tag); // now update the user final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); ri.record.put(kField_Tag + "." + tagType, tag); ri.write(); } @Override public DrumlinAccountPersistence.tagInfo getRequestTagByTag(String requestTag, String tagType) throws DrumlinAccountsException { try { final recordInfo tagRecord = readIndexedObject(kIndex_RequestTags, requestTag, tagType); if (tagRecord == null) return null; // no such tag return new tagInfo(requestTag, tagRecord.record.getString(kField_UserId), tagRecord.record.getLong(kField_Expires)); } catch (JSONException e) { throw new DrumlinAccountsException(e); } } @Override public DrumlinAccountPersistence.tagInfo getRequestTagByUser(String userId, String tagType) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); final String tag = ri.record.optString(kField_Tag + "." + tagType, null); if (tag == null) return null; return getRequestTagByTag(tag, tagType); } @Override public void deleteRequestByTag(String tag, String tagType) throws DrumlinAccountsException { try { final recordInfo tagRecord = readIndexedObject(kIndex_RequestTags, tag, tagType); removeFromIndex(kIndex_RequestTags, tag); if (tagRecord != null) { fStore.delete(tagRecord.location); } } catch (IOException e) { throw new DrumlinAccountsException(e); } } @Override public void deleteRequestTagOnUser(String userId, String tag, String tagType) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); if (ri != null && null != ri.record.remove(kField_Tag + "." + tagType)) { ri.write(); } } @Override public DrumlinApiKey loadApiKey(String incomingKey) throws DrumlinAccountsException { try { final recordInfo ri = readIndexedObject(kIndex_ApiKeys, incomingKey, kValue_ApiKey); if (ri != null) { final String apiKey = ri.record.getString(kField_ApiKey); final String apiSecret = ri.record.getString(kField_ApiSecret); final String apiUser = ri.record.optString(kField_UserId, null); final String apiAcct = ri.record.optString(kField_AcctId, null); return new DrumlinApiKey() { @Override public String getKey() { return apiKey; } @Override public String getSecret() { return apiSecret; } @Override public DrumlinApiKey.type getAccessType() { return (apiUser != null ? DrumlinApiKey.type.USER : DrumlinApiKey.type.ACCOUNT); } @Override public String getAccount() { return apiAcct; } @Override public String getUser() { return apiUser; } }; } return null; } catch (JSONException e) { throw new DrumlinAccountsException(e); } } @Override public Collection<DrumlinApiKey> loadApiKeysForUser(String userId) throws DrumlinAccountsException { JSONArray apiKeys = null; final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); if (ri != null) { apiKeys = ri.record.optJSONArray(kField_ApiKey); } final LinkedList<DrumlinApiKey> result = new LinkedList<DrumlinApiKey>(); if (apiKeys != null) { for (int i = 0; i < apiKeys.length(); i++) { try { final DrumlinApiKey a = loadApiKey(apiKeys.getString(i)); if (a != null) { result.add(a); } } catch (JSONException e) { log.warn("Couldn't load string " + i + " from API key list on user " + userId + "."); } } } return result; } @Override public void storeApiKey(DrumlinApiKey apiKey) throws DrumlinAccountsException { final JSONObject o = new JSONObject(); o.put(kField_Type, kValue_ApiKey); o.put(kField_ApiKey, apiKey.getKey()); o.put(kField_ApiSecret, apiKey.getSecret()); final String userId = apiKey.getUser(); if (userId != null) { o.put(kField_UserId, userId); final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); if (ri != null) { JSONArray apiKeys = ri.record.optJSONArray(kField_ApiKey); if (apiKeys == null) { apiKeys = new JSONArray(); ri.record.put(kField_ApiKey, apiKeys); } apiKeys.put(apiKey.getKey()); ri.write(); } else { log.warn("While writing API key " + apiKey.getKey() + ", no such user " + userId + "."); } } final String a = apiKey.getAccount(); if (a != null) { o.put(kField_AcctId, a); } writeAndIndex(o, kIndex_ApiKeys, apiKey.getKey()); } @Override public void deleteApiKey(DrumlinApiKey apiKey) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_ApiKeys, apiKey.getKey(), kValue_ApiKey); if (ri != null) { try { fStore.delete(ri.location); } catch (IOException e) { throw new DrumlinAccountsException(e); } } } @Override public void setApiSecret(final String apiKey, final String apiSecret) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_ApiKeys, apiKey, kValue_ApiKey); if (ri != null) { ri.record.put(kField_ApiSecret, apiSecret); ri.write(); } } @Override public void setUserValue(String userId, String key, String value) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); if (ri != null) { ri.record.put(key, value); ri.write(); } } @Override public String getUserValue(String userId, String key) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); if (ri != null) return ri.record.optString(kField_Enabled, null); return null; } @Override public void removeUservalue(String userId, String key) throws DrumlinAccountsException { final recordInfo ri = readIndexedObject(kIndex_UserList, userId, kValue_User); if (ri != null) ri.record.remove(kField_Enabled); } private final rrJsonObjectFile fStore; private final JSONObject fIndex; private static final String kField_Type = "type"; private static final String kValue_User = "user"; private static final String kValue_Acct = "account"; private static final String kValue_ApiKey = "apiKey"; private static final String kField_Enabled = "enabled"; private static final String kField_UserId = "userId"; private static final String kField_Tag = "tag"; private static final String kField_AcctId = "acctId"; private static final String kField_Expires = "expires"; private static final String kField_Salt = "salt"; private static final String kField_HashedPassword = "hashedPassword"; private static final String kField_ApiKey = "apiKey"; private static final String kField_ApiSecret = "apiSecret"; private static final String kIndex_ApiKeys = "apiKeys"; private static final String kIndex_UserList = "userList"; private static final String kIndex_AcctList = "acctList"; private static final String kIndex_RequestTags = "resets"; private static final org.slf4j.Logger log = rrLogSetup.getLog(exampleAcctPersistence.class); private long writeAndIndex(JSONObject o, String indexName, String keyName) throws DrumlinAccountsException { try { final long loc = fStore.write(o); putInIndex(indexName, keyName, loc); return loc; } catch (IOException e) { throw new DrumlinAccountsException(e); } } private void putInIndex(String indexName, String keyName, long recordLoc) throws DrumlinAccountsException { try { final JSONObject o = fIndex.getJSONObject(indexName); o.put(keyName, recordLoc); fStore.overwrite(fStore.indexToAddress(0), fIndex); } catch (JSONException e) { throw new DrumlinAccountsException(e); } catch (IOException e) { throw new DrumlinAccountsException(e); } } private void removeFromIndex(String indexName, String keyName) throws DrumlinAccountsException { try { final JSONObject o = fIndex.getJSONObject(indexName); o.remove(keyName); fStore.overwrite(fStore.indexToAddress(0), fIndex); } catch (JSONException e) { throw new DrumlinAccountsException(e); } catch (IOException e) { throw new DrumlinAccountsException(e); } } private class recordInfo { public recordInfo(long loc, JSONObject rec) { location = loc; record = rec; } public void write() throws DrumlinAccountsException { try { fStore.overwrite(location, record); } catch (IOException e) { throw new DrumlinAccountsException(e); } } public final long location; public final JSONObject record; } private recordInfo readIndexedObject(String indexName, String key, String type) throws DrumlinAccountsException { try { final long index = readIndex(indexName, key); if (index >= 0) { final JSONObject result = fStore.read(index); if (result.optString(kField_Type, "").equals(type)) { return new recordInfo(index, result); } } return null; } catch (IOException e) { throw new DrumlinAccountsException(e); } } private long readIndex(String indexName, String keyName) throws DrumlinAccountsException { try { final JSONObject o = fIndex.getJSONObject(indexName); return o.optLong(keyName, -1); } catch (JSONException e) { throw new DrumlinAccountsException(e); } } }