1 /*-
2  * Copyright (c) 2002, 2020 Oracle and/or its affiliates.  All rights reserved.
3  *
4  * See the file LICENSE for license information.
5  *
6  */
7 
8 package com.sleepycat.persist.test;
9 
10 import static com.sleepycat.persist.model.Relationship.MANY_TO_ONE;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertNull;
14 import static org.junit.Assert.assertTrue;
15 import static org.junit.Assert.fail;
16 
17 import java.io.File;
18 
19 import org.junit.After;
20 import org.junit.Before;
21 import org.junit.Test;
22 
23 import com.sleepycat.db.DatabaseException;
24 import com.sleepycat.db.Environment;
25 import com.sleepycat.db.EnvironmentConfig;
26 import com.sleepycat.db.Transaction;
27 import com.sleepycat.db.util.DualTestCase;
28 import com.sleepycat.persist.EntityCursor;
29 import com.sleepycat.persist.EntityStore;
30 import com.sleepycat.persist.PrimaryIndex;
31 import com.sleepycat.persist.SecondaryIndex;
32 import com.sleepycat.persist.StoreConfig;
33 import com.sleepycat.persist.model.AnnotationModel;
34 import com.sleepycat.persist.model.Entity;
35 import com.sleepycat.persist.model.EntityModel;
36 import com.sleepycat.persist.model.Persistent;
37 import com.sleepycat.persist.model.PrimaryKey;
38 import com.sleepycat.persist.model.SecondaryKey;
39 import com.sleepycat.util.test.SharedTestUtils;
40 import com.sleepycat.util.test.TestEnv;
41 
42 public class SubclassIndexTest extends DualTestCase {
43 
44     private File envHome;
45     private Environment env;
46     private EntityStore store;
47 
48     @Before
setUp()49     public void setUp()
50         throws Exception {
51 
52         envHome = SharedTestUtils.getTestDir();
53         super.setUp();
54     }
55 
56     @After
tearDown()57     public void tearDown()
58         throws Exception {
59 
60         super.tearDown();
61         envHome = null;
62         env = null;
63     }
64 
open()65     private void open()
66         throws DatabaseException {
67 
68         EnvironmentConfig envConfig = TestEnv.TXN.getConfig();
69         envConfig.setAllowCreate(true);
70         env = create(envHome, envConfig);
71 
72         EntityModel model = new AnnotationModel();
73         model.registerClass(Manager.class);
74         model.registerClass(SalariedManager.class);
75 
76         StoreConfig storeConfig = new StoreConfig();
77         storeConfig.setModel(model);
78         storeConfig.setAllowCreate(true);
79         storeConfig.setTransactional(true);
80         store = new EntityStore(env, "foo", storeConfig);
81     }
82 
close()83     private void close()
84         throws DatabaseException {
85 
86         store.close();
87         store = null;
88         close(env);
89         env = null;
90     }
91 
92     @Test
testSubclassIndex()93     public void testSubclassIndex()
94         throws DatabaseException {
95 
96         open();
97 
98         PrimaryIndex<String, Employee> employeesById =
99             store.getPrimaryIndex(String.class, Employee.class);
100 
101         employeesById.put(new Employee("1"));
102         employeesById.put(new Manager("2", "a"));
103         employeesById.put(new Manager("3", "a"));
104         employeesById.put(new Manager("4", "b"));
105 
106         Employee e;
107         Manager m;
108 
109         e = employeesById.get("1");
110         assertNotNull(e);
111         assertTrue(!(e instanceof Manager));
112 
113         /* Ensure DB exists BEFORE calling getSubclassIndex. [#15247] */
114         PersistTestUtils.assertDbExists
115             (true, env, "foo", Employee.class.getName(), "dept");
116 
117         /* Normal use: Subclass index for a key in the subclass. */
118         SecondaryIndex<String, String, Manager> managersByDept =
119             store.getSubclassIndex
120                 (employeesById, Manager.class, String.class, "dept");
121 
122         m = managersByDept.get("a");
123         assertNotNull(m);
124         assertEquals("2", m.id);
125 
126         m = managersByDept.get("b");
127         assertNotNull(m);
128         assertEquals("4", m.id);
129 
130         Transaction txn = env.beginTransaction(null, null);
131         EntityCursor<Manager> managers = managersByDept.entities(txn, null);
132         try {
133             m = managers.next();
134             assertNotNull(m);
135             assertEquals("2", m.id);
136             m = managers.next();
137             assertNotNull(m);
138             assertEquals("3", m.id);
139             m = managers.next();
140             assertNotNull(m);
141             assertEquals("4", m.id);
142             m = managers.next();
143             assertNull(m);
144         } finally {
145             managers.close();
146             txn.commit();
147         }
148 
149         /* Getting a subclass index for the entity class is also allowed. */
150         store.getSubclassIndex
151             (employeesById, Employee.class, String.class, "other");
152 
153         /* Getting a subclass index for a base class key is not allowed. */
154         try {
155             store.getSubclassIndex
156                 (employeesById, Manager.class, String.class, "other");
157             fail();
158         } catch (IllegalArgumentException expected) {
159         }
160 
161         close();
162     }
163 
164     /**
165      * Previously this tested that a secondary key database was added only
166      * AFTER storing the first instance of the subclass that defines the key.
167      * Now that we require registering the subclass up front, the database is
168      * created up front also.  So this test is somewhat less useful, but still
169      * nice to have around.  [#16399]
170      */
171     @Test
testAddSecKey()172     public void testAddSecKey()
173         throws DatabaseException {
174 
175         open();
176         PrimaryIndex<String, Employee> employeesById =
177             store.getPrimaryIndex(String.class, Employee.class);
178         employeesById.put(new Employee("1"));
179         assertTrue(hasEntityKey("dept"));
180         close();
181 
182         open();
183         employeesById = store.getPrimaryIndex(String.class, Employee.class);
184         assertTrue(hasEntityKey("dept"));
185         employeesById.put(new Manager("2", "a"));
186         assertTrue(hasEntityKey("dept"));
187         close();
188 
189         open();
190         assertTrue(hasEntityKey("dept"));
191         close();
192 
193         open();
194         employeesById = store.getPrimaryIndex(String.class, Employee.class);
195         assertTrue(hasEntityKey("salary"));
196         employeesById.put(new SalariedManager("3", "a", "111"));
197         assertTrue(hasEntityKey("salary"));
198         close();
199 
200         open();
201         assertTrue(hasEntityKey("dept"));
202         assertTrue(hasEntityKey("salary"));
203         close();
204     }
205 
hasEntityKey(String keyName)206     private boolean hasEntityKey(String keyName) {
207         return store.getModel().
208                getRawType(Employee.class.getName()).
209                getEntityMetadata().
210                getSecondaryKeys().
211                keySet().
212                contains(keyName);
213     }
214 
215     @Entity
216     private static class Employee {
217 
218         @PrimaryKey
219         String id;
220 
221         @SecondaryKey(relate=MANY_TO_ONE)
222         String other;
223 
Employee(String id)224         Employee(String id) {
225             this.id = id;
226         }
227 
Employee()228         private Employee() {}
229     }
230 
231     @Persistent
232     private static class Manager extends Employee {
233 
234         @SecondaryKey(relate=MANY_TO_ONE)
235         String dept;
236 
Manager(String id, String dept)237         Manager(String id, String dept) {
238             super(id);
239             this.dept = dept;
240         }
241 
Manager()242         private Manager() {}
243     }
244 
245     @Persistent
246     private static class SalariedManager extends Manager {
247 
248         @SecondaryKey(relate=MANY_TO_ONE)
249         String salary;
250 
SalariedManager(String id, String dept, String salary)251         SalariedManager(String id, String dept, String salary) {
252             super(id, dept);
253             this.salary = salary;
254         }
255 
SalariedManager()256         private SalariedManager() {}
257     }
258 }
259