content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  
  package org.apache.cayenne.access;
  
  import java.sql.Types;
  import java.util.Arrays;
  import java.util.List;
  
  import org.apache.cayenne.Cayenne;
  import org.apache.cayenne.di.Inject;
  import org.apache.cayenne.query.ObjectSelect;
  import org.apache.cayenne.query.SQLTemplate;
  import org.apache.cayenne.query.SortOrder;
  import org.apache.cayenne.test.jdbc.TableHelper;
  import org.apache.cayenne.testdo.inheritance_people.AbstractPerson;
  import org.apache.cayenne.testdo.inheritance_people.Address;
  import org.apache.cayenne.testdo.inheritance_people.ClientCompany;
  import org.apache.cayenne.testdo.inheritance_people.CustomerRepresentative;
  import org.apache.cayenne.testdo.inheritance_people.Department;
  import org.apache.cayenne.testdo.inheritance_people.Employee;
  import org.apache.cayenne.testdo.inheritance_people.Manager;
  import org.apache.cayenne.testdo.inheritance_people.PersonNotes;
  import org.apache.cayenne.unit.di.DataChannelInterceptor;
  import org.apache.cayenne.unit.di.runtime.PeopleProjectCase;
  import org.junit.Before;
  import org.junit.Test;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertSame;
  import static org.junit.Assert.assertTrue;
  
  public class SingleTableInheritanceIT extends PeopleProjectCase {
  
      @Inject
      private DataContext context;
  
      @Inject
      private DataContext context2;
  
      @Inject
      private DataChannelInterceptor queryBlocker;
  
      private TableHelper tPerson;
      private TableHelper tAddress;
      private TableHelper tClientCompany;
      private TableHelper tDepartment;
  
      @Before
  	public void setUp() {
  		tAddress = new TableHelper(dbHelper, "ADDRESS");
  		tAddress.setColumns("ADDRESS_ID", "CITY", "PERSON_ID");
  
  		tClientCompany = new TableHelper(dbHelper, "CLIENT_COMPANY");
  		tClientCompany.setColumns("CLIENT_COMPANY_ID", "NAME");
  
  		tDepartment = new TableHelper(dbHelper, "DEPARTMENT");
  		tDepartment.setColumns("DEPARTMENT_ID", "NAME");
  
  		tPerson = new TableHelper(dbHelper, "PERSON").setColumns("PERSON_ID", "NAME", "PERSON_TYPE", "SALARY",
  				"CLIENT_COMPANY_ID", "DEPARTMENT_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.CHAR,
  				Types.FLOAT, Types.INTEGER, Types.INTEGER);
  	}
  
      private void create2PersonDataSet() throws Exception {
          tPerson.insert(1, "E1", "EE", null, null, null);
          tPerson.insert(2, "E2", "EM", null, null, null);
      }
  
      private void create5PersonDataSet() throws Exception {
          tPerson.insert(1, "E1", "EE", null, null, null);
          tPerson.insert(2, "E2", "EM", null, null, null);
          tPerson.insert(3, "E3", "EE", null, null, null);
          tPerson.insert(4, "E4", "EM", null, null, null);
          tPerson.insert(5, "E5", "EE", null, null, null);
      }
  
      private void createSelectDataSet() throws Exception {
          tPerson.insert(1, "e1", "EE", 20000, null, null);
          tPerson.insert(2, "e2", "EE", 25000, null, null);
          tPerson.insert(3, "e3", "EE", 28000, null, null);
          tPerson.insert(4, "m1", "EM", 30000, null, null);
          tPerson.insert(5, "m2", "EM", 40000, null, null);
  
          tClientCompany.insert(1, "Citibank");
          tPerson.insert(6, "c1", "C", null, 1, null);
      }
  
      private void createEmployeeAddressDataSet() throws Exception {
          tPerson.insert(1, "e1", "EE", 20000, null, null);
          tAddress.insert(1, "New York", 1);
      }
  
      private void createManagerAddressDataSet() throws Exception {
          tPerson.insert(4, "m1", "EM", 30000, null, null);
          tAddress.insert(1, "New York", 4);
      }
  
      private void createRepCompanyDataSet() throws Exception {
          tClientCompany.insert(1, "Citibank");
          tPerson.insert(6, "c1", "C", null, 1, null);
      }
  
      private void createDepartmentEmployeesDataSet() throws Exception {
          tDepartment.insert(1, "Accounting");
  
          tPerson.insert(7, "John", "EE", 25000, null, 1);
          tPerson.insert(8, "Susan", "EE", 50000, null, 1);
          tPerson.insert(9, "Kelly", "EM", 100000, null, 1);
      }
  
      @Test
      public void testMatchingOnSuperAttributes() throws Exception {
          create2PersonDataSet();
  
          // fetch on leaf, but match on a super attribute
          List<Manager> results = ObjectSelect.query(Manager.class)
                  .and(AbstractPerson.NAME.eq("E2"))
                  .select(context);
          assertEquals(1, results.size());
          assertEquals("E2", results.get(0).getName());
      }
  
      @Test
      public void testMatchingOnSuperAttributesWithPrefetch() throws Exception {
          create2PersonDataSet();
  
          // fetch on leaf, but match on a super attribute
          List<Employee> results = ObjectSelect.query(Employee.class)
                  .prefetch(Employee.TO_DEPARTMENT.disjoint())
                  .and(AbstractPerson.NAME.eq("E2"))
                  .select(context);
          assertEquals(1, results.size());
          assertEquals("E2", results.get(0).getName());
      }
  
      @Test
      public void testPaginatedQueries() throws Exception {
          create5PersonDataSet();
  
          List<AbstractPerson> results = ObjectSelect.query(AbstractPerson.class)
                  .orderBy("db:" + AbstractPerson.PERSON_ID_PK_COLUMN, SortOrder.ASCENDING)
                  .pageSize(3)
                  .select(context);
          assertEquals(5, results.size());
  
          assertTrue(results.get(0) instanceof Employee);
  
          // this is where things would blow up per CAY-1142
          assertTrue(results.get(1) instanceof Manager);
  
          assertTrue(results.get(3) instanceof Manager);
          assertTrue(results.get(4) instanceof Employee);
      }
  
      @Test
      public void testRelationshipToAbstractSuper() {
          context
                  .performGenericQuery(new SQLTemplate(
                          AbstractPerson.class,
                          "INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (1, 'AA', 'EE')"));
  
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (1, 'AA', 1)"));
  
          PersonNotes note = Cayenne.objectForPK(context, PersonNotes.class, 1);
          assertNotNull(note);
          assertNotNull(note.getPerson());
          assertTrue(note.getPerson() instanceof Employee);
      }
  
      @Test
      public void testRelationshipAbstractFromSuperPrefetchingJoint() {
          context
                  .performGenericQuery(new SQLTemplate(
                          AbstractPerson.class,
                          "INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (3, 'AA', 'EE')"));
  
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'AA', 3)"));
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (4, 'BB', 3)"));
  
          ObjectSelect<AbstractPerson> query = ObjectSelect.query(AbstractPerson.class)
                  .prefetch(AbstractPerson.NOTES.joint());
  
          final AbstractPerson person = (AbstractPerson) Cayenne.objectForQuery(
                  context,
                  query);
  
          assertTrue(person instanceof Employee);
  
          queryBlocker.runWithQueriesBlocked(() -> {
              assertEquals(2, person.getNotes().size());
  
              String[] names = new String[2];
              names[0] = person.getNotes().get(0).getNotes();
              names[1] = person.getNotes().get(1).getNotes();
              List<String> nameSet = Arrays.asList(names);
  
              assertTrue(nameSet.contains("AA"));
              assertTrue(nameSet.contains("BB"));
          });
      }
  
      @Test
      public void testRelationshipAbstractFromSuperPrefetchingDisjoint() {
          context
                  .performGenericQuery(new SQLTemplate(
                          AbstractPerson.class,
                          "INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (3, 'AA', 'EE')"));
  
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'AA', 3)"));
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (4, 'BB', 3)"));
  
          ObjectSelect<AbstractPerson> query = ObjectSelect.query(AbstractPerson.class)
                  .prefetch(AbstractPerson.NOTES.disjoint());
  
          final AbstractPerson person = (AbstractPerson) Cayenne.objectForQuery(
                  context,
                  query);
          assertTrue(person instanceof Employee);
  
          queryBlocker.runWithQueriesBlocked(() -> {
              assertEquals(2, person.getNotes().size());
  
              String[] names = new String[2];
              names[0] = person.getNotes().get(0).getNotes();
              names[1] = person.getNotes().get(1).getNotes();
              List<String> nameSet = Arrays.asList(names);
  
              assertTrue(nameSet.contains("AA"));
              assertTrue(nameSet.contains("BB"));
          });
      }
  
      @Test
      public void testRelationshipAbstractToSuperPrefetchingDisjoint() {
          context
                  .performGenericQuery(new SQLTemplate(
                          AbstractPerson.class,
                          "INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (2, 'AA', 'EE')"));
  
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (2, 'AA', 2)"));
  
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'BB', 2)"));
  
          List<PersonNotes> notes = ObjectSelect.query(PersonNotes.class)
                  .prefetch(PersonNotes.PERSON.disjoint())
                  .orderBy(PersonNotes.NOTES.asc())
                  .select(context);
          assertEquals(2, notes.size());
          final PersonNotes note = notes.get(0);
  
          queryBlocker.runWithQueriesBlocked(() -> assertEquals("AA", note.getPerson().getName()));
      }
  
      @Test
      public void testRelationshipAbstractToSuperPrefetchingJoint() {
          context
                  .performGenericQuery(new SQLTemplate(
                          AbstractPerson.class,
                          "INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (3, 'AA', 'EE')"));
  
          context.performGenericQuery(new SQLTemplate(
                  PersonNotes.class,
                  "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'AA', 3)"));
  
          ObjectSelect<PersonNotes> query = ObjectSelect.query(PersonNotes.class)
                  .prefetch(PersonNotes.PERSON.joint());
  
          final PersonNotes note = (PersonNotes) Cayenne.objectForQuery(context, query);
  
          queryBlocker.runWithQueriesBlocked(() -> assertEquals("AA", note.getPerson().getName()));
  
      }
  
      @Test
      public void testSave() throws Exception {
          ClientCompany company = context.newObject(ClientCompany.class);
          company.setName("Boeing");
  
          CustomerRepresentative rep = context.newObject(CustomerRepresentative.class);
          rep.setName("Joe Schmoe");
          rep.setToClientCompany(company);
          rep.setPersonType("C");
  
          Employee employee = context.newObject(Employee.class);
          employee.setName("Our Joe Schmoe");
          employee.setPersonType("E");
  
          context.commitChanges();
          context.invalidateObjects(company, rep, employee);
  
          List<?> reps = ObjectSelect.query(CustomerRepresentative.class)
                  .select(context2);
  
          assertEquals(1, reps.size());
          assertEquals(1, countObjectOfClass(reps, CustomerRepresentative.class));
      }
  
      /**
       * Tests that to-one relationship produces correct subclass.
       */
      @Test
      public void testEmployeeAddress() throws Exception {
          createEmployeeAddressDataSet();
  
          List<?> addresses = ObjectSelect.query(Address.class)
                  .select(context);
  
          assertEquals(1, addresses.size());
          Address address = (Address) addresses.get(0);
          assertSame(Employee.class, address.getToEmployee().getClass());
      }
  
      /**
       * Tests that to-one relationship produces correct subclass.
       */
      @Test
      public void testManagerAddress() throws Exception {
          createManagerAddressDataSet();
  
          List<Address> addresses = ObjectSelect.query(Address.class)
                  .select(context);
  
          assertEquals(1, addresses.size());
          Address address = (Address) addresses.get(0);
          Employee e = address.getToEmployee();
  
          assertSame(Manager.class, e.getClass());
      }
  
      @Test
      public void testCAY592() throws Exception {
          createManagerAddressDataSet();
  
          List<Address> addresses = ObjectSelect.query(Address.class)
                  .select(context);
  
          assertEquals(1, addresses.size());
          Address address = addresses.get(0);
          Employee e = address.getToEmployee();
  
          // CAY-592 - make sure modification of the address in a parallel context
          // doesn't mess up the Manager
  
          e = (Employee) Cayenne.objectForPK(context2, e.getObjectId());
          address = e.getAddresses().get(0);
  
          assertSame(e, address.getToEmployee());
          address.setCity("XYZ");
          assertSame(e, address.getToEmployee());
      }
  
      /**
       * Tests that to-one relationship produces correct subclass.
       */
      @Test
      public void testRepCompany() throws Exception {
          createRepCompanyDataSet();
  
          List<ClientCompany> companies = ObjectSelect.query(ClientCompany.class)
                  .select(context);
  
          assertEquals(1, companies.size());
          ClientCompany company = companies.get(0);
          List<?> reps = company.getRepresentatives();
          assertEquals(1, reps.size());
          assertSame(CustomerRepresentative.class, reps.get(0).getClass());
      }
  
      /**
       * Tests that to-many relationship produces correct subclasses.
       */
      @Test
      public void testDepartmentEmployees() throws Exception {
          createDepartmentEmployeesDataSet();
  
          List<Department> departments = ObjectSelect.query(Department.class)
                  .select(context);
  
          assertEquals(1, departments.size());
          Department dept = departments.get(0);
          List<?> employees = dept.getEmployees();
          assertEquals(3, employees.size());
          assertEquals(3, countObjectOfClass(employees, Employee.class));
          assertEquals(1, countObjectOfClass(employees, Manager.class));
      }
  
      @Test
      public void testSelectInheritanceResolving() throws Exception {
          createSelectDataSet();
  
          List<AbstractPerson> abstractPpl = ObjectSelect.query(AbstractPerson.class)
                  .select(context);
          assertEquals(6, abstractPpl.size());
  
          assertEquals(1, countObjectOfClass(abstractPpl, CustomerRepresentative.class));
          assertEquals(5, countObjectOfClass(abstractPpl, Employee.class));
          assertEquals(2, countObjectOfClass(abstractPpl, Manager.class));
      }
  
      /**
       * Returns a number of objects of a particular class and subclasses in the list.
       */
      private int countObjectOfClass(List<?> objects, Class<?> aClass) {
          int i = 0;
  
          for (Object next : objects) {
              if (aClass.isAssignableFrom(next.getClass())) {
                  i++;
              }
          }
          return i;
      }
  }
  