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.DeleteAction.NULLIFY;
11 import static com.sleepycat.persist.model.Relationship.ONE_TO_MANY;
12 import static com.sleepycat.persist.model.Relationship.ONE_TO_ONE;
13 import static org.junit.Assert.assertEquals;
14 import static org.junit.Assert.assertNotNull;
15 import static org.junit.Assert.assertTrue;
16 import static org.junit.Assert.fail;
17 
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Locale;
22 
23 import org.junit.After;
24 import org.junit.Test;
25 import org.junit.runner.RunWith;
26 import org.junit.runners.Parameterized;
27 import org.junit.runners.Parameterized.Parameters;
28 
29 import com.sleepycat.db.DatabaseConfig;
30 import com.sleepycat.db.DatabaseException;
31 import com.sleepycat.db.SecondaryConfig;
32 import com.sleepycat.db.SequenceConfig;
33 import com.sleepycat.persist.EntityStore;
34 import com.sleepycat.persist.PrimaryIndex;
35 import com.sleepycat.persist.SecondaryIndex;
36 import com.sleepycat.persist.StoreConfig;
37 import com.sleepycat.persist.model.AnnotationModel;
38 import com.sleepycat.persist.model.Entity;
39 import com.sleepycat.persist.model.KeyField;
40 import com.sleepycat.persist.model.Persistent;
41 import com.sleepycat.persist.model.PersistentProxy;
42 import com.sleepycat.persist.model.PrimaryKey;
43 import com.sleepycat.persist.model.SecondaryKey;
44 import com.sleepycat.util.test.TxnTestCase;
45 
46 /**
47  * Negative tests.
48  *
49  * @author Mark Hayes
50  */
51 @RunWith(Parameterized.class)
52 public class NegativeTest extends TxnTestCase {
53 
54     @Parameters
genParams()55     public static List<Object[]> genParams() {
56         return getTxnParams(null, false);
57     }
58 
NegativeTest(String type)59     public NegativeTest(String type){
60         initEnvConfig();
61         txnType = type;
62         isTransactional = (txnType != TXN_NULL);
63         customName = txnType;
64     }
65 
66     private EntityStore store;
67 
open()68     private void open()
69         throws DatabaseException {
70 
71         open(null);
72     }
73 
open(Class<ProxyExtendsEntity> clsToRegister)74     private void open(Class<ProxyExtendsEntity> clsToRegister)
75         throws DatabaseException {
76 
77         StoreConfig config = new StoreConfig();
78         config.setAllowCreate(envConfig.getAllowCreate());
79         config.setTransactional(envConfig.getTransactional());
80 
81         if (clsToRegister != null) {
82             AnnotationModel model = new AnnotationModel();
83             model.registerClass(clsToRegister);
84             config.setModel(model);
85         }
86 
87         store = new EntityStore(env, "test", config);
88     }
89 
close()90     private void close()
91         throws DatabaseException {
92 
93         store.close();
94         store = null;
95     }
96 
97     @After
tearDown()98     public void tearDown()
99         throws Exception {
100 
101         if (store != null) {
102             try {
103                 store.close();
104             } catch (Throwable e) {
105                 System.out.println("tearDown: " + e);
106             }
107             store = null;
108         }
109         super.tearDown();
110     }
111 
112     @Test
testBadKeyClass1()113     public void testBadKeyClass1()
114         throws DatabaseException {
115 
116         open();
117         try {
118             store.getPrimaryIndex(BadKeyClass1.class, UseBadKeyClass1.class);
119             fail();
120         } catch (IllegalArgumentException expected) {
121             assertTrue(expected.getMessage().indexOf("@KeyField") >= 0);
122         }
123         close();
124     }
125 
126     /** Missing @KeyField in composite key class. */
127     @Persistent
128     static class BadKeyClass1 {
129 
130         private int f1;
131     }
132 
133     @Entity
134     static class UseBadKeyClass1 {
135 
136         @PrimaryKey
137         private final BadKeyClass1 f1 = new BadKeyClass1();
138 
139         @SecondaryKey(relate=ONE_TO_ONE)
140         private final BadKeyClass1 f2 = new BadKeyClass1();
141     }
142 
143     @Test
testBadSequenceKeys()144     public void testBadSequenceKeys()
145         throws DatabaseException {
146 
147         open();
148         try {
149             store.getPrimaryIndex(Boolean.class, BadSequenceKeyEntity1.class);
150             fail();
151         } catch (IllegalArgumentException expected) {
152             assertTrue(expected.getMessage().indexOf
153                 ("Type not allowed for sequence") >= 0);
154         }
155         try {
156             store.getPrimaryIndex(BadSequenceKeyEntity2.Key.class,
157                      BadSequenceKeyEntity2.class);
158             fail();
159         } catch (IllegalArgumentException expected) {
160             assertTrue(expected.getMessage().indexOf
161                 ("Type not allowed for sequence") >= 0);
162         }
163         try {
164             store.getPrimaryIndex(BadSequenceKeyEntity3.Key.class,
165                      BadSequenceKeyEntity3.class);
166             fail();
167         } catch (IllegalArgumentException expected) {
168             assertTrue(expected.getMessage().indexOf
169                 ("A composite key class used with a sequence may contain " +
170                  "only a single key field")>= 0);
171         }
172         close();
173     }
174 
175     /** Boolean not allowed for sequence key. */
176     @Entity
177     static class BadSequenceKeyEntity1 {
178 
179         @PrimaryKey(sequence="X")
180         private boolean key;
181     }
182 
183     /** Composite key with non-integer field not allowed for sequence key. */
184     @Entity
185     static class BadSequenceKeyEntity2 {
186 
187         @PrimaryKey(sequence="X")
188         private Key key;
189 
190         @Persistent
191         static class Key {
192             @KeyField(1)
193             boolean key;
194         }
195     }
196 
197     /** Composite key with multiple key fields not allowed for sequence key. */
198     @Entity
199     static class BadSequenceKeyEntity3 {
200 
201         @PrimaryKey(sequence="X")
202         private Key key;
203 
204         @Persistent
205         static class Key {
206             @KeyField(1)
207             int key;
208             @KeyField(2)
209             int key2;
210         }
211     }
212 
213     /**
214      * A proxied object may not current contain a field that references the
215      * parent proxy.  [#15815]
216      */
217     @Test
testProxyNestedRef()218     public void testProxyNestedRef()
219         throws DatabaseException {
220 
221         open();
222         PrimaryIndex<Integer, ProxyNestedRef> index = store.getPrimaryIndex
223             (Integer.class, ProxyNestedRef.class);
224         ProxyNestedRef entity = new ProxyNestedRef();
225         entity.list.add(entity.list);
226         try {
227             index.put(entity);
228             fail();
229         } catch (IllegalArgumentException expected) {
230             assertTrue(expected.getMessage().indexOf
231                 ("Cannot embed a reference to a proxied object") >= 0);
232         }
233         close();
234     }
235 
236     @Entity
237     static class ProxyNestedRef {
238 
239         @PrimaryKey
240         private int key;
241 
242         ArrayList<Object> list = new ArrayList<Object>();
243     }
244 
245     /**
246      * Disallow primary keys on entity subclasses.  [#15757]
247      */
248     @Test
testEntitySubclassWithPrimaryKey()249     public void testEntitySubclassWithPrimaryKey()
250         throws DatabaseException {
251 
252         open();
253         PrimaryIndex<Integer, EntitySuperClass> index = store.getPrimaryIndex
254             (Integer.class, EntitySuperClass.class);
255         EntitySuperClass e1 = new EntitySuperClass(1, "one");
256         index.put(e1);
257         assertEquals(e1, index.get(1));
258         EntitySubClass e2 = new EntitySubClass(2, "two", "foo", 9);
259         try {
260             index.put(e2);
261             fail();
262         } catch (IllegalArgumentException e) {
263             assertTrue(e.getMessage().contains
264                 ("PrimaryKey may not appear on an Entity subclass"));
265         }
266         assertEquals(e1, index.get(1));
267         close();
268     }
269 
270     @Entity
271     static class EntitySuperClass {
272 
273         @PrimaryKey
274         private int x;
275 
276         private String y;
277 
EntitySuperClass(int x, String y)278         EntitySuperClass(int x, String y) {
279             assert y != null;
280             this.x = x;
281             this.y = y;
282         }
283 
EntitySuperClass()284         private EntitySuperClass() {}
285 
286         @Override
toString()287         public String toString() {
288             return "x=" + x + " y=" + y;
289         }
290 
291         @Override
equals(Object other)292         public boolean equals(Object other) {
293             if (other instanceof EntitySuperClass) {
294                 EntitySuperClass o = (EntitySuperClass) other;
295                 return x == o.x && y.equals(o.y);
296             } else {
297                 return false;
298             }
299         }
300     }
301 
302     @Persistent
303     static class EntitySubClass extends EntitySuperClass {
304 
305         @PrimaryKey
306         private String foo;
307 
308         private int z;
309 
EntitySubClass(int x, String y, String foo, int z)310         EntitySubClass(int x, String y, String foo, int z) {
311             super(x, y);
312             assert foo != null;
313             this.foo = foo;
314             this.z = z;
315         }
316 
EntitySubClass()317         private EntitySubClass() {}
318 
319         @Override
toString()320         public String toString() {
321             return super.toString() + " z=" + z;
322         }
323 
324         @Override
equals(Object other)325         public boolean equals(Object other) {
326             if (other instanceof EntitySubClass) {
327                 EntitySubClass o = (EntitySubClass) other;
328                 return super.equals(o) && z == o.z;
329             } else {
330                 return false;
331             }
332         }
333     }
334 
335     /**
336      * Disallow storing null entities. [#19085]
337      */
338     @Test
testNullEntity()339     public void testNullEntity()
340         throws DatabaseException {
341 
342         open();
343         PrimaryIndex<Integer, EntitySuperClass> index = store.getPrimaryIndex
344             (Integer.class, EntitySuperClass.class);
345         try {
346             index.put(null);
347             fail();
348         } catch (IllegalArgumentException expected) {
349         }
350         try {
351             index.sortedMap().put(1, null);
352             fail();
353         } catch (IllegalArgumentException expected) {
354         }
355         close();
356     }
357 
358     /**
359      * Disallow embedded entity classes and subclasses.  [#16077]
360      */
361     @Test
testEmbeddedEntity()362     public void testEmbeddedEntity()
363         throws DatabaseException {
364 
365         open();
366         PrimaryIndex<Integer, EmbeddingEntity> index = store.getPrimaryIndex
367             (Integer.class, EmbeddingEntity.class);
368         EmbeddingEntity e1 = new EmbeddingEntity(1, null);
369         index.put(e1);
370         assertEquals(e1, index.get(1));
371 
372         EmbeddingEntity e2 =
373             new EmbeddingEntity(2, new EntitySuperClass(2, "two"));
374         try {
375             index.put(e2);
376             fail();
377         } catch (IllegalArgumentException e) {
378             assertTrue(e.getMessage().contains
379                 ("References to entities are not allowed"));
380         }
381 
382         EmbeddingEntity e3 = new EmbeddingEntity
383             (3, new EmbeddedEntitySubClass(3, "three", "foo", 9));
384         try {
385             index.put(e3);
386             fail();
387         } catch (IllegalArgumentException e) {
388             assertTrue(e.toString(), e.getMessage().contains
389                 ("References to entities are not allowed"));
390         }
391 
392         assertEquals(e1, index.get(1));
393         close();
394     }
395 
396     @Entity
397     static class EmbeddingEntity {
398 
399         @PrimaryKey
400         private int x;
401 
402         private EntitySuperClass y;
403 
404         /* References to self are allowed. [#17525] */
405         private EmbeddingEntity self;
406 
EmbeddingEntity(int x, EntitySuperClass y)407         EmbeddingEntity(int x, EntitySuperClass y) {
408             this.x = x;
409             this.y = y;
410             this.self = this;
411         }
412 
EmbeddingEntity()413         private EmbeddingEntity() {}
414 
415         @Override
toString()416         public String toString() {
417             return "x=" + x + " y=" + y;
418         }
419 
420         @Override
equals(Object other)421         public boolean equals(Object other) {
422             if (other instanceof EmbeddingEntity) {
423                 EmbeddingEntity o = (EmbeddingEntity) other;
424                 return x == o.x &&
425                        ((y == null) ? (o.y == null) : y.equals(o.y));
426             } else {
427                 return false;
428             }
429         }
430     }
431 
432     @Persistent
433     static class EmbeddedEntitySubClass extends EntitySuperClass {
434 
435         private String foo;
436 
437         private int z;
438 
EmbeddedEntitySubClass(int x, String y, String foo, int z)439         EmbeddedEntitySubClass(int x, String y, String foo, int z) {
440             super(x, y);
441             assert foo != null;
442             this.foo = foo;
443             this.z = z;
444         }
445 
EmbeddedEntitySubClass()446         private EmbeddedEntitySubClass() {}
447 
448         @Override
toString()449         public String toString() {
450             return super.toString() + " z=" + z;
451         }
452 
453         @Override
equals(Object other)454         public boolean equals(Object other) {
455             if (other instanceof EmbeddedEntitySubClass) {
456                 EmbeddedEntitySubClass o = (EmbeddedEntitySubClass) other;
457                 return super.equals(o) && z == o.z;
458             } else {
459                 return false;
460             }
461         }
462     }
463 
464     /**
465      * Disallow SecondaryKey collection with no type parameter. [#15950]
466      */
467     @Test
testTypelessKeyCollection()468     public void testTypelessKeyCollection()
469         throws DatabaseException {
470 
471         open();
472         try {
473             store.getPrimaryIndex
474                 (Integer.class, TypelessKeyCollectionEntity.class);
475             fail();
476         } catch (IllegalArgumentException e) {
477             assertTrue(e.toString(), e.getMessage().contains
478                 ("Collection typed secondary key field must have a " +
479                  "single generic type argument and a wildcard or type " +
480                  "bound is not allowed"));
481         }
482         close();
483     }
484 
485     @Entity
486     static class TypelessKeyCollectionEntity {
487 
488         @PrimaryKey
489         private int x;
490 
491         @SecondaryKey(relate=ONE_TO_MANY)
492         private final Collection keys = new ArrayList();
493 
TypelessKeyCollectionEntity(int x)494         TypelessKeyCollectionEntity(int x) {
495             this.x = x;
496         }
497 
TypelessKeyCollectionEntity()498         private TypelessKeyCollectionEntity() {}
499     }
500 
501     /**
502      * Disallow a persistent proxy that extends an entity.  [#15950]
503      */
504     @Test
testProxyEntity()505     public void testProxyEntity()
506         throws DatabaseException {
507 
508         try {
509             open(ProxyExtendsEntity.class);
510             fail();
511         } catch (IllegalArgumentException e) {
512             assertTrue(e.toString(), e.getMessage().contains
513                 ("A proxy may not be an entity"));
514         }
515     }
516 
517     @Persistent(proxyFor=Locale.class)
518     static class ProxyExtendsEntity
519         extends EntitySuperClass
520         implements PersistentProxy<Locale> {
521 
522         String language;
523         String country;
524         String variant;
525 
initializeProxy(Locale object)526         public void initializeProxy(Locale object) {
527             language = object.getLanguage();
528             country = object.getCountry();
529             variant = object.getVariant();
530         }
531 
convertProxy()532         public Locale convertProxy() {
533             return new Locale(language, country, variant);
534         }
535     }
536 
537     /**
538      * Wrapper type not allowed for nullified foreign key.
539      */
540     @Test
testBadNullifyKey()541     public void testBadNullifyKey()
542         throws DatabaseException {
543 
544         open();
545         try {
546             store.getPrimaryIndex(Integer.class, BadNullifyKeyEntity1.class);
547             fail();
548         } catch (IllegalArgumentException expected) {
549             assertTrue(expected.getMessage().indexOf
550                 ("NULLIFY may not be used with primitive fields") >= 0);
551         }
552         close();
553     }
554 
555     @Entity
556     static class BadNullifyKeyEntity1 {
557 
558         @PrimaryKey
559         private int key;
560 
561         @SecondaryKey(relate=ONE_TO_ONE,
562                       relatedEntity=BadNullifyKeyEntity2.class,
563                       onRelatedEntityDelete=NULLIFY)
564         private int secKey; // Should be Integer, not int.
565     }
566 
567     @Entity
568     static class BadNullifyKeyEntity2 {
569 
570         @PrimaryKey
571         private int key;
572     }
573 
574     /**
575      * @Persistent not allowed on an enum.
576      */
577     @Test
testPersistentEnum()578     public void testPersistentEnum()
579         throws DatabaseException {
580 
581         open();
582         try {
583             store.getPrimaryIndex(Integer.class, PersistentEnumEntity.class);
584             fail();
585         } catch (IllegalArgumentException expected) {
586             assertTrue(expected.getMessage().indexOf
587                 ("not allowed for enum, interface, or primitive") >= 0);
588         }
589         close();
590     }
591 
592     @Entity
593     static class PersistentEnumEntity {
594 
595         @PrimaryKey
596         private int key;
597 
598         @Persistent
599         enum MyEnum {X, Y, Z};
600 
601         MyEnum f1;
602     }
603 
604     /**
605      * Disallow a reference to an interface marked @Persistent.
606      */
607     @Test
testPersistentInterface()608     public void testPersistentInterface()
609         throws DatabaseException {
610 
611         open();
612         try {
613             store.getPrimaryIndex(Integer.class,
614                                   PersistentInterfaceEntity1.class);
615             fail();
616         } catch (IllegalArgumentException expected) {
617             assertTrue(expected.getMessage().indexOf
618                 ("not allowed for enum, interface, or primitive") >= 0);
619         }
620         close();
621     }
622 
623     @Entity
624     static class PersistentInterfaceEntity1 {
625 
626         @PrimaryKey
627         private int key;
628 
629         @SecondaryKey(relate=ONE_TO_ONE,
630                       relatedEntity=PersistentInterfaceEntity2.class)
631         private int secKey; // Should be Integer, not int.
632     }
633 
634     @Persistent
635     interface PersistentInterfaceEntity2 {
636     }
637 
638     /**
639      * Disallow reference to @Persistent inner class.
640      */
641     @Test
testPersistentInnerClass()642     public void testPersistentInnerClass()
643         throws DatabaseException {
644 
645         open();
646         try {
647             store.getPrimaryIndex(Integer.class,
648                                   PersistentInnerClassEntity1.class);
649             fail();
650         } catch (IllegalArgumentException expected) {
651             assertTrue(expected.getMessage().indexOf
652                 ("Inner classes not allowed") >= 0);
653         }
654         close();
655     }
656 
657     @Entity
658     static class PersistentInnerClassEntity1 {
659 
660         @PrimaryKey
661         private int key;
662 
663         private PersistentInnerClass f;
664     }
665 
666     /* An inner (non-static) class is illegal. */
667     @Persistent
668     class PersistentInnerClass {
669 
670         private int x;
671     }
672 
673     /**
674      * Disallow @Entity inner class.
675      */
676     @Test
testSetConfigAfterOpen()677     public void testSetConfigAfterOpen()
678         throws DatabaseException {
679 
680         open();
681         PrimaryIndex<Integer, SetConfigAfterOpenEntity> priIndex =
682             store.getPrimaryIndex(Integer.class,
683                                   SetConfigAfterOpenEntity.class);
684         SecondaryIndex<Integer, Integer, SetConfigAfterOpenEntity> secIndex =
685             store.getSecondaryIndex(priIndex, Integer.class, "skey");
686 
687         DatabaseConfig priConfig =
688             store.getPrimaryConfig(SetConfigAfterOpenEntity.class);
689         assertNotNull(priConfig);
690         try {
691             store.setPrimaryConfig(SetConfigAfterOpenEntity.class, priConfig);
692             fail();
693         } catch (IllegalStateException expected) {
694             assertTrue(expected.getMessage().indexOf
695                 ("Cannot set config after DB is open") >= 0);
696         }
697 
698         SecondaryConfig secConfig =
699             store.getSecondaryConfig(SetConfigAfterOpenEntity.class, "skey");
700         assertNotNull(secConfig);
701         try {
702             store.setSecondaryConfig(SetConfigAfterOpenEntity.class, "skey",
703                                      secConfig);
704             fail();
705         } catch (IllegalStateException expected) {
706             assertTrue(expected.getMessage().indexOf
707                 ("Cannot set config after DB is open") >= 0);
708         }
709 
710         SequenceConfig seqConfig = store.getSequenceConfig("foo");
711         assertNotNull(seqConfig);
712         try {
713             store.setSequenceConfig("foo", seqConfig);
714             fail();
715         } catch (IllegalStateException expected) {
716             assertTrue(expected.getMessage().indexOf
717                 ("Cannot set config after Sequence is open") >= 0);
718         }
719 
720         close();
721     }
722 
723     @Entity
724     static class SetConfigAfterOpenEntity {
725 
726         @PrimaryKey(sequence="foo")
727         private int key;
728 
729         @SecondaryKey(relate=ONE_TO_ONE)
730         int skey;
731     }
732 }
733