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.HashMap;
021    import java.util.Map;
022    
023    import javax.jdo.PersistenceManager;
024    
025    import org.cumulus4j.store.EncryptionHandler;
026    
027    /**
028     * <p>
029     * Factory for creating (or looking up) specific {@link IndexEntry} implementations.
030     * </p><p>
031     * It is optional to implement a specific factory. For most use cases, it is sufficient to
032     * use the {@link DefaultIndexEntryFactory} (which is used, if the extension specifies the
033     * attribute <code>index-entry-type</code>), but you can alternatively specify a custom
034     * factory via the extension-attribute <code>index-entry-factory-type</code>.
035     * </p><p>
036     * If you specify a custom
037     * factory, you must omit (or leave empty) the <code>index-entry-type</code>!
038     * </p>
039     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
040     */
041    public abstract class IndexEntryFactory
042    {
043            /**
044             * Get the concrete implementation class (sub-class) of {@link IndexEntry} managed by this factory.
045             * @return the concrete implementation class of {@link IndexEntry} managed by this factory.
046             */
047            public abstract Class<? extends IndexEntry> getIndexEntryClass();
048    
049            /**
050             * Get an {@link IndexEntry} for the specified unique key fields or <code>null</code>, if no such instance
051             * exists.
052             * @param pmIndex the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
053             * @param fieldMeta the meta-data of the field to query. Must not be <code>null</code>.
054             * @param indexKey the indexed value to search for. Might be <code>null</code> (<code>null</code> can be indexed).
055             * @return the matching {@link IndexEntry} or <code>null</code>.
056             */
057            public IndexEntry getIndexEntry(PersistenceManager pmIndex, FieldMeta fieldMeta, Object indexKey)
058            {
059                    if (pmIndex == null)
060                            throw new IllegalArgumentException("pm == null");
061    
062                    if (fieldMeta == null)
063                            throw new IllegalArgumentException("fieldMeta == null");
064    
065                    Class<? extends IndexEntry> indexEntryClass = getIndexEntryClass();
066                    javax.jdo.Query q = pmIndex.newQuery(indexEntryClass);
067                    q.setUnique(true);
068                    q.setFilter(
069                                    "this.fieldMeta == :fieldMeta && " +
070                                    "this.indexKey == :indexKey"
071                    );
072                    Map<String, Object> params = new HashMap<String, Object>();
073                    params.put("fieldMeta", fieldMeta);
074                    params.put("indexKey", indexKey);
075                    return indexEntryClass.cast(q.executeWithMap(params));
076            }
077    
078            /**
079             * Get an existing {@link IndexEntry} just like {@link #getIndexEntry(PersistenceManager, FieldMeta, Object)}
080             * or create one, if it does not yet exist.
081             * @param pmIndex the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
082             * @param fieldMeta the meta-data of the field to query. Must not be <code>null</code>.
083             * @param indexKey the indexed value to search for. Might be <code>null</code> (<code>null</code> can be indexed).
084             * @return the matching {@link IndexEntry} (never <code>null</code>).
085             */
086            public IndexEntry createIndexEntry(PersistenceManager pmIndex, FieldMeta fieldMeta, Object indexKey)
087            {
088                    IndexEntry result = getIndexEntry(pmIndex, fieldMeta, indexKey);
089                    if (result == null) {
090                            try {
091                                    result = getIndexEntryClass().newInstance();
092                            } catch (InstantiationException e) {
093                                    throw new RuntimeException(e);
094                            } catch (IllegalAccessException e) {
095                                    throw new RuntimeException(e);
096                            }
097                            result.setFieldMeta(fieldMeta);
098                            result.setIndexKey(indexKey);
099    
100                            // We persist *after* setting all values, because that improves performance:
101                            // This way, there is only one INSERT instead of one INSERT AND one UPDATE for each new
102                            // index entry. The MovieQueryTest.importDataCsv() is around 10% faster when using MySQL
103                            // (approximately 60 sec vs. 66 sec).
104                            // However, when dumping the plaintexts for debugging, we need the indexEntryID already *before*
105                            // encryption. Hence, we persist here, if the DEBUG_DUMP flag is set.
106                            // Marco :-)
107                            if (EncryptionHandler.DEBUG_DUMP)
108                                    result = pmIndex.makePersistent(result);
109                    }
110    
111                    return result;
112            }
113    }