1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: PersistKeyCreator.java,v 1.1 2008/02/07 17:12:27 mark Exp $
7 */
8
9package com.sleepycat.persist.impl;
10
11import java.util.Collection;
12import java.util.Set;
13
14import com.sleepycat.bind.tuple.TupleBase;
15import com.sleepycat.db.DatabaseEntry;
16import com.sleepycat.db.DatabaseException;
17import com.sleepycat.db.ForeignMultiKeyNullifier;
18import com.sleepycat.db.SecondaryDatabase;
19import com.sleepycat.db.SecondaryKeyCreator;
20import com.sleepycat.db.SecondaryMultiKeyCreator;
21import com.sleepycat.persist.model.EntityMetadata;
22import com.sleepycat.persist.model.Relationship;
23import com.sleepycat.persist.model.SecondaryKeyMetadata;
24import com.sleepycat.persist.raw.RawObject;
25
26/**
27 * A persistence secondary key creator/nullifier.  This class always uses
28 * rawAccess=true to avoid depending on the presence of the proxy class.
29 *
30 * @author Mark Hayes
31 */
32public class PersistKeyCreator implements SecondaryKeyCreator,
33                                          SecondaryMultiKeyCreator,
34                                          ForeignMultiKeyNullifier {
35
36    static boolean isManyType(Class cls) {
37        return cls.isArray() || Collection.class.isAssignableFrom(cls);
38    }
39
40    private Catalog catalog;
41    private int priKeyFormatId;
42    private String keyName;
43    private Format keyFormat;
44    private boolean toMany;
45
46    /**
47     * Creates a key creator/nullifier for a given entity class and key name.
48     */
49    public PersistKeyCreator(Catalog catalog,
50                             EntityMetadata entityMeta,
51                             String keyClassName,
52                             SecondaryKeyMetadata secKeyMeta) {
53        this.catalog = catalog;
54        Format priKeyFormat =
55            catalog.getFormat(entityMeta.getPrimaryKey().getClassName());
56        priKeyFormatId = priKeyFormat.getId();
57        keyName = secKeyMeta.getKeyName();
58        keyFormat = catalog.getFormat(keyClassName);
59        if (keyFormat == null) {
60            throw new IllegalArgumentException
61                ("Not a key class: " + keyClassName);
62        }
63        if (keyFormat.isPrimitive()) {
64            throw new IllegalArgumentException
65                ("Use a primitive wrapper class instead of class: " +
66                 keyFormat.getClassName());
67        }
68        Relationship rel = secKeyMeta.getRelationship();
69        toMany = (rel == Relationship.ONE_TO_MANY ||
70                  rel == Relationship.MANY_TO_MANY);
71    }
72
73    public boolean createSecondaryKey(SecondaryDatabase secondary,
74				      DatabaseEntry key,
75				      DatabaseEntry data,
76				      DatabaseEntry result)
77	throws DatabaseException {
78
79        if (toMany) {
80            throw new IllegalStateException();
81        }
82        KeyLocation loc = moveToKey(key, data);
83        if (loc != null) {
84            RecordOutput output = new RecordOutput
85                (catalog, true /*rawAccess*/);
86            loc.format.copySecKey(loc.input, output);
87            TupleBase.outputToEntry(output, result);
88            return true;
89        } else {
90            /* Key field is not present or null. */
91            return false;
92        }
93    }
94
95    public void createSecondaryKeys(SecondaryDatabase secondary,
96				    DatabaseEntry key,
97				    DatabaseEntry data,
98				    Set results)
99	throws DatabaseException {
100
101        if (!toMany) {
102            throw new IllegalStateException();
103        }
104        KeyLocation loc = moveToKey(key, data);
105        if (loc != null) {
106            loc.format.copySecMultiKey(loc.input, keyFormat, results);
107        }
108        /* Else key field is not present or null. */
109    }
110
111    public boolean nullifyForeignKey(SecondaryDatabase secondary,
112                                     DatabaseEntry key,
113                                     DatabaseEntry data,
114                                     DatabaseEntry secKey)
115	throws DatabaseException {
116
117        /* Deserialize the entity and get its current class format. */
118        RawObject entity = (RawObject) PersistEntityBinding.readEntity
119            (catalog, key, data, true /*rawAccess*/);
120        Format entityFormat = (Format) entity.getType();
121
122        /*
123         * Set the key to null.  For a TO_MANY key, pass the key object to be
124         * removed from the array/collection.
125         */
126        Object secKeyObject = null;
127        if (toMany) {
128            secKeyObject = PersistKeyBinding.readKey
129                (keyFormat, catalog, secKey.getData(), secKey.getOffset(),
130                 secKey.getSize(), true /*rawAccess*/);
131        }
132        if (entityFormat.nullifySecKey
133            (catalog, entity, keyName, secKeyObject)) {
134
135            /*
136             * Using the current format for the entity, serialize the modified
137             * entity back to the data entry.
138             */
139            PersistEntityBinding.writeEntity
140                (entityFormat, catalog, entity, data, true /*rawAccess*/);
141            return true;
142        } else {
143            /* Key field is not present or null. */
144            return false;
145        }
146    }
147
148    /**
149     * Returns the location from which the secondary key field can be copied.
150     */
151    private KeyLocation moveToKey(DatabaseEntry priKey, DatabaseEntry data) {
152
153        RecordInput input = new RecordInput
154            (catalog, true /*rawAccess*/, priKey, priKeyFormatId,
155             data.getData(), data.getOffset(), data.getSize());
156        int formatId = input.readPackedInt();
157        Format entityFormat = catalog.getFormat(formatId);
158        Format fieldFormat = entityFormat.skipToSecKey(input, keyName);
159        if (fieldFormat != null) {
160            /* Returns null if key field is null. */
161            return input.getKeyLocation(fieldFormat);
162        } else {
163            /* Key field is not present in this class. */
164            return null;
165        }
166    }
167}
168