001 /* 002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org 003 * Copyright (C) 2011 NightLabs Consulting GmbH 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 package org.cumulus4j.store.model; 019 020 import java.util.ArrayList; 021 import java.util.Collections; 022 import java.util.HashMap; 023 import java.util.List; 024 import java.util.Map; 025 026 import javax.jdo.PersistenceManager; 027 028 import org.cumulus4j.store.Cumulus4jStoreManager; 029 import org.cumulus4j.store.EncryptionHandler; 030 import org.cumulus4j.store.crypto.CryptoContext; 031 import org.datanucleus.store.ExecutionContext; 032 033 /** 034 * <p> 035 * Factory for creating (or looking up) specific {@link IndexEntry} implementations. 036 * </p><p> 037 * It is optional to implement a specific factory. For most use cases, it is sufficient to 038 * use the {@link DefaultIndexEntryFactory} (which is used, if the extension specifies the 039 * attribute <code>index-entry-type</code>), but you can alternatively specify a custom 040 * factory via the extension-attribute <code>index-entry-factory-type</code>. 041 * </p><p> 042 * If you specify a custom 043 * factory, you must omit (or leave empty) the <code>index-entry-type</code>! 044 * </p> 045 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 046 */ 047 public abstract class IndexEntryFactory 048 { 049 /** 050 * Get the concrete implementation class (sub-class) of {@link IndexEntry} managed by this factory. 051 * @return the concrete implementation class of {@link IndexEntry} managed by this factory. 052 */ 053 public abstract Class<? extends IndexEntry> getIndexEntryClass(); 054 055 public List<IndexEntry> getIndexEntriesIncludingSubClasses(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object indexKey) 056 { 057 ExecutionContext ec = cryptoContext.getExecutionContext(); 058 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) ec.getStoreManager(); 059 List<ClassMeta> classMetaWithSubClassMetas = storeManager.getClassMetaWithSubClassMetas(ec, classMeta); 060 return getIndexEntries(cryptoContext, pmIndex, fieldMeta, classMetaWithSubClassMetas, indexKey); 061 } 062 063 public List<IndexEntry> getIndexEntries(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, List<ClassMeta> classMetas, Object indexKey) 064 { 065 if (pmIndex == null) 066 throw new IllegalArgumentException("pm == null"); 067 068 if (fieldMeta == null) 069 throw new IllegalArgumentException("fieldMeta == null"); 070 071 if (classMetas == null) 072 throw new IllegalArgumentException("classMetas == null"); 073 074 if (classMetas.isEmpty()) { 075 throw new IllegalArgumentException("classMetas is empty"); // hmmm... I think this should never happen. 076 // return Collections.emptyList(); 077 } 078 079 if (classMetas.size() == 1) { 080 IndexEntry indexEntry = getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMetas.get(0), indexKey); 081 if (indexEntry == null) 082 return Collections.emptyList(); 083 else 084 return Collections.singletonList(indexEntry); 085 } 086 // List<IndexEntry> result = new ArrayList<IndexEntry>(classMetas.size()); 087 // for (ClassMeta classMeta : classMetas) { 088 // IndexEntry indexEntry = getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, indexKey); 089 // if (indexEntry != null) 090 // result.add(indexEntry); 091 // } 092 // return result; 093 094 Class<? extends IndexEntry> indexEntryClass = getIndexEntryClass(); 095 javax.jdo.Query q = pmIndex.newQuery(indexEntryClass); 096 Map<String, Object> params = new HashMap<String, Object>(); 097 q.setFilter( 098 "this.keyStoreRefID == :keyStoreRefID && " + 099 "this.fieldMeta_fieldID == :fieldMeta_fieldID && " + 100 // ":classMetas.contains(this.classMeta) && " + 101 ClassMetaDAO.getMultiClassMetaOrFilterPart(params, classMetas) + " && " + 102 "this.indexKey == :indexKey" 103 ); 104 params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID()); 105 params.put("fieldMeta_fieldID", fieldMeta.getFieldID()); 106 // params.put("classMetas", classMetas); 107 params.put("indexKey", indexKey); 108 @SuppressWarnings("unchecked") 109 List<IndexEntry> result = (List<IndexEntry>) q.executeWithMap(params); 110 result = Collections.unmodifiableList(new ArrayList<IndexEntry>(result)); // consistent with emptyList + singletonList above (both read-only) 111 q.closeAll(); 112 return result; 113 } 114 115 /** 116 * Get an {@link IndexEntry} for the specified unique key fields or <code>null</code>, if no such instance 117 * exists. 118 * @param cryptoContext the crypto-context. 119 * @param pmIndex the backend-<code>PersistenceManager</code>. Must not be <code>null</code>. 120 * @param fieldMeta the meta-data of the field to query. Must not be <code>null</code>. 121 * @param classMeta TODO 122 * @param indexKey the indexed value to search for. Might be <code>null</code> (<code>null</code> can be indexed). 123 * @return the matching {@link IndexEntry} or <code>null</code>. 124 */ 125 public IndexEntry getIndexEntry(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object indexKey) 126 { 127 if (pmIndex == null) 128 throw new IllegalArgumentException("pm == null"); 129 130 if (fieldMeta == null) 131 throw new IllegalArgumentException("fieldMeta == null"); 132 133 if (classMeta == null) 134 throw new IllegalArgumentException("classMeta == null"); 135 136 Class<? extends IndexEntry> indexEntryClass = getIndexEntryClass(); 137 javax.jdo.Query q = pmIndex.newQuery(indexEntryClass); 138 q.setUnique(true); 139 q.setFilter( 140 "this.keyStoreRefID == :keyStoreRefID && " + 141 "this.fieldMeta_fieldID == :fieldMeta_fieldID && " + 142 "this.classMeta_classID == :classMeta_classID && " + 143 "this.indexKey == :indexKey" 144 ); 145 Map<String, Object> params = new HashMap<String, Object>(); 146 params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID()); 147 params.put("fieldMeta_fieldID", fieldMeta.getFieldID()); 148 params.put("classMeta_classID", classMeta.getClassID()); 149 params.put("indexKey", indexKey); 150 return indexEntryClass.cast(q.executeWithMap(params)); 151 } 152 153 /** 154 * Get an existing {@link IndexEntry} just like {@link #getIndexEntry(CryptoContext, PersistenceManager, FieldMeta, ClassMeta, Object)} 155 * or create one, if it does not yet exist. 156 * @param cryptoContext TODO 157 * @param pmIndex the backend-<code>PersistenceManager</code>. Must not be <code>null</code>. 158 * @param fieldMeta the meta-data of the field to query. Must not be <code>null</code>. 159 * @param classMeta TODO 160 * @param indexKey the indexed value to search for. Might be <code>null</code> (<code>null</code> can be indexed). 161 * @return the matching {@link IndexEntry} (never <code>null</code>). 162 */ 163 public IndexEntry createIndexEntry(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object indexKey) 164 { 165 IndexEntry result = getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, indexKey); 166 if (result == null) { 167 try { 168 result = getIndexEntryClass().newInstance(); 169 } catch (InstantiationException e) { 170 throw new RuntimeException(e); 171 } catch (IllegalAccessException e) { 172 throw new RuntimeException(e); 173 } 174 result.setFieldMeta(fieldMeta); 175 result.setClassMeta(classMeta); 176 result.setKeyStoreRefID(cryptoContext.getKeyStoreRefID()); 177 result.setIndexKey(indexKey); 178 179 // We persist *after* setting all values, because that improves performance: 180 // This way, there is only one INSERT instead of one INSERT AND one UPDATE for each new 181 // index entry. The MovieQueryTest.importDataCsv() is around 10% faster when using MySQL 182 // (approximately 60 sec vs. 66 sec). 183 // However, when dumping the plaintexts for debugging, we need the indexEntryID already *before* 184 // encryption. Hence, we persist here, if the DEBUG_DUMP flag is set. 185 // Marco :-) 186 if (EncryptionHandler.DEBUG_DUMP) 187 result = pmIndex.makePersistent(result); 188 } 189 190 return result; 191 } 192 }