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.map;
  
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.net.URL;
  import java.util.Collections;
  import java.util.List;
  
  import org.apache.cayenne.CayenneRuntimeException;
  import org.apache.cayenne.configuration.DataMapLoader;
  import org.apache.cayenne.configuration.EmptyConfigurationNodeVisitor;
  import org.apache.cayenne.di.Inject;
  import org.apache.cayenne.exp.ExpressionException;
  import org.apache.cayenne.exp.path.CayennePath;
  import org.apache.cayenne.resource.URLResource;
  import org.apache.cayenne.runtime.CayenneRuntime;
  import org.apache.cayenne.testdo.inheritance_vertical.Iv2Sub1;
  import org.apache.cayenne.unit.di.runtime.CayenneProjects;
  import org.apache.cayenne.unit.di.runtime.RuntimeCase;
  import org.apache.cayenne.unit.di.runtime.UseCayenneRuntime;
  import org.apache.cayenne.util.Util;
  import org.apache.cayenne.util.XMLEncoder;
  import org.junit.Before;
  import org.junit.Test;
  
  import static org.junit.Assert.*;
  
  @UseCayenneRuntime(CayenneProjects.TESTMAP_PROJECT)
  public class ObjRelationshipIT extends RuntimeCase {
  
      @Inject
      private CayenneRuntime runtime;
  
      private DbEntity artistDBEntity;
      private DbEntity artistExhibitDBEntity;
      private DbEntity exhibitDBEntity;
      private DbEntity paintingDbEntity;
      private DbEntity galleryDBEntity;
  
      @Before
      public void setUp() throws Exception {
          EntityResolver resolver = runtime.getDataDomain().getEntityResolver();
  
          artistDBEntity = resolver.getDbEntity("ARTIST");
          artistExhibitDBEntity = resolver.getDbEntity("ARTIST_EXHIBIT");
          exhibitDBEntity = resolver.getDbEntity("EXHIBIT");
          paintingDbEntity = resolver.getDbEntity("PAINTING");
          galleryDBEntity = resolver.getDbEntity("GALLERY");
      }
  
      @Test
      public void testEncodeAsXML() {
          StringWriter buffer = new StringWriter();
          PrintWriter out = new PrintWriter(buffer);
          XMLEncoder encoder = new XMLEncoder(out);
  
          DataMap map = new DataMap("M");
          ObjEntity source = new ObjEntity("S");
          ObjEntity target = new ObjEntity("T");
          map.addObjEntity(source);
          map.addObjEntity(target);
  
          ObjRelationship r = new ObjRelationship("X");
          r.setSourceEntity(source);
          r.setTargetEntityName("T");
          r.setCollectionType("java.util.Map");
          r.setMapKey("bla");
  
          r.encodeAsXML(encoder, new EmptyConfigurationNodeVisitor());
          out.close();
  
          String lineBreak = System.getProperty("line.separator");
  
          assertEquals("<obj-relationship name=\"X\" source=\"S\" target=\"T\" "
                  + "collection-type=\"java.util.Map\" map-key=\"bla\"/>" + lineBreak, buffer.getBuffer().toString());
      }
  
      @Test
      public void testCollectionType() {
          ObjRelationship r = new ObjRelationship("X");
          assertNull(r.getCollectionType());
          r.setCollectionType("java.util.Map");
          assertEquals("java.util.Map", r.getCollectionType());
      }
  
      @Test
      public void testSerializability() throws Exception {
          ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Artist");
  
          // start with "to many"
          ObjRelationship r1 = artistObjEnt.getRelationship("paintingArray");
  
          ObjRelationship r2 = Util.cloneViaSerialization(r1);
          assertEquals(r1.getName(), r2.getName());
          assertEquals(r1.getDbRelationshipPath(), r2.getDbRelationshipPath());
      }
  
      @Test
      public void testGetReverseDbRelationshipPath() {
          ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Artist");
          ObjEntity paintingObjEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Painting");
  
          // start with "to many"
          ObjRelationship r1 = artistObjEnt.getRelationship("paintingArray");
  
          assertEquals("toArtist", r1.getReverseDbRelationshipPath().value());
  
          ObjRelationship r2 = paintingObjEnt.getRelationship("toArtist");
  
          assertEquals("paintingArray", r2.getReverseDbRelationshipPath().value());
      }
  
      @Test
      public void testSetDbRelationshipPath() {
          ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Artist");
  
          ObjRelationship r = new ObjRelationship("r");
          r.setSourceEntity(artistObjEnt);
          r.setDbRelationshipPath("paintingArray");
          assertEquals("paintingArray", r.getDbRelationshipPath().value());
      }
  
      @Test
      public void testRefreshFromPath() {
          ObjRelationship relationship = new ObjRelationship();
  
          // attempt to resolve must fail - relationship is outside of context,
          // plus the path is random
          try {
              relationship.setDbRelationshipPath("dummy.path");
              fail("set random path should have failed.");
          } catch (CayenneRuntimeException ex) {
              // expected
          }
  
          DataMap map = new DataMap();
          ObjEntity entity = new ObjEntity("Test");
          map.addObjEntity(entity);
  
          relationship.setSourceEntity(entity);
          // attempt to resolve must fail - relationship is outside of context,
          // plus the path is random
          try {
              relationship.refreshFromPath(CayennePath.of("dummy.path"), false);
              fail("refresh over a dummy path should have failed.");
          } catch (ExpressionException ex) {
              // expected
          }
  
          // finally assemble ObjEntity to make the path valid
          DbEntity dbEntity1 = new DbEntity("TEST1");
          DbEntity dbEntity2 = new DbEntity("TEST2");
          DbEntity dbEntity3 = new DbEntity("TEST3");
          map.addDbEntity(dbEntity1);
          map.addDbEntity(dbEntity2);
          map.addDbEntity(dbEntity3);
          entity.setDbEntityName("TEST1");
          DbRelationship dummyR = new DbRelationship("dummy");
          dummyR.setTargetEntityName("TEST2");
          dummyR.setSourceEntity(dbEntity1);
          DbRelationship pathR = new DbRelationship("path");
          pathR.setTargetEntityName("TEST3");
          pathR.setSourceEntity(dbEntity2);
          dbEntity1.addRelationship(dummyR);
          dbEntity2.addRelationship(pathR);
  
          relationship.refreshFromPath(CayennePath.of("dummy.path"), false);
  
          List<DbRelationship> resolvedPath = relationship.getDbRelationships();
          assertEquals(2, resolvedPath.size());
          assertSame(dummyR, resolvedPath.get(0));
          assertSame(pathR, resolvedPath.get(1));
      }
  
      @Test
      public void testCalculateToMany() {
          // assemble fixture....
          DataMap map = new DataMap();
          ObjEntity entity = new ObjEntity("Test");
          map.addObjEntity(entity);
  
          DbEntity dbEntity1 = new DbEntity("TEST1");
          DbEntity dbEntity2 = new DbEntity("TEST2");
          DbEntity dbEntity3 = new DbEntity("TEST3");
          map.addDbEntity(dbEntity1);
          map.addDbEntity(dbEntity2);
          map.addDbEntity(dbEntity3);
          entity.setDbEntityName("TEST1");
          DbRelationship dummyR = new DbRelationship("dummy");
          dummyR.setTargetEntityName("TEST2");
          dummyR.setSourceEntity(dbEntity1);
          DbRelationship pathR = new DbRelationship("path");
          pathR.setTargetEntityName("TEST3");
          pathR.setSourceEntity(dbEntity2);
          dbEntity1.addRelationship(dummyR);
          dbEntity2.addRelationship(pathR);
  
          ObjRelationship relationship = new ObjRelationship();
          relationship.setSourceEntity(entity);
  
          // test how toMany changes dependending on the underlying
          // DbRelationships
          // add DbRelationships directly to avoid events to test
          // "calculateToMany"
          relationship.dbRelationships.add(dummyR);
          assertFalse(relationship.isToMany());
  
          dummyR.setToMany(true);
          relationship.recalculateToManyValue();
          assertTrue(relationship.isToMany());
  
          dummyR.setToMany(false);
          relationship.recalculateToManyValue();
          assertFalse(relationship.isToMany());
  
          // test chain
          relationship.dbRelationships.add(pathR);
          assertFalse(relationship.isToMany());
  
          pathR.setToMany(true);
          relationship.recalculateToManyValue();
          assertTrue(relationship.isToMany());
      }
  
      @Test
      public void testCalculateToManyFromPath() {
          // assemble fixture....
          DataMap map = new DataMap();
          ObjEntity entity = new ObjEntity("Test");
          map.addObjEntity(entity);
  
          DbEntity dbEntity1 = new DbEntity("TEST1");
          DbEntity dbEntity2 = new DbEntity("TEST2");
          DbEntity dbEntity3 = new DbEntity("TEST3");
          map.addDbEntity(dbEntity1);
          map.addDbEntity(dbEntity2);
          map.addDbEntity(dbEntity3);
          entity.setDbEntityName("TEST1");
          DbRelationship dummyR = new DbRelationship("dummy");
          dummyR.setTargetEntityName("TEST2");
          dummyR.setSourceEntity(dbEntity1);
          DbRelationship pathR = new DbRelationship("path");
          pathR.setTargetEntityName("TEST3");
          pathR.setSourceEntity(dbEntity2);
          dbEntity1.addRelationship(dummyR);
          dbEntity2.addRelationship(pathR);
  
          ObjRelationship relationship = new ObjRelationship();
          relationship.setSourceEntity(entity);
  
          // test how toMany changes when the path is set as a string
  
          relationship.setDbRelationshipPath("dummy");
          assertFalse(relationship.isToMany());
  
          dummyR.setToMany(true);
          relationship.setDbRelationshipPath((String)null);
          relationship.setDbRelationshipPath("dummy");
          assertTrue(relationship.isToMany());
  
          dummyR.setToMany(false);
          relationship.setDbRelationshipPath((String)null);
          relationship.setDbRelationshipPath("dummy");
          assertFalse(relationship.isToMany());
  
          // test chain
          relationship.setDbRelationshipPath((String)null);
          relationship.setDbRelationshipPath("dummy.path");
          assertFalse(relationship.isToMany());
  
          pathR.setToMany(true);
          relationship.setDbRelationshipPath((String)null);
          relationship.setDbRelationshipPath("dummy.path");
          assertTrue(relationship.isToMany());
      }
  
      @Test
      public void testTargetEntity() throws Exception {
          ObjRelationship relationship = new ObjRelationship("some_rel");
          relationship.setTargetEntityName("targ");
  
          try {
              relationship.getTargetEntity();
              fail("Without a container, getTargetEntity() must fail.");
          } catch (CayenneRuntimeException ex) {
              // expected
          }
  
          // assemble container
          DataMap map = new DataMap();
          ObjEntity src = new ObjEntity("src");
          map.addObjEntity(src);
  
          src.addRelationship(relationship);
          assertNull(relationship.getTargetEntity());
  
          ObjEntity target = new ObjEntity("targ");
          map.addObjEntity(target);
  
          assertSame(target, relationship.getTargetEntity());
      }
  
      @Test
      public void testGetReverseRel1() {
  
          ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Artist");
          ObjEntity paintingObjEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Painting");
  
          // start with "to many"
          ObjRelationship r1 = artistObjEnt.getRelationship("paintingArray");
          ObjRelationship r2 = r1.getReverseRelationship();
  
          assertNotNull(r2);
          assertSame(paintingObjEnt.getRelationship("toArtist"), r2);
      }
  
      @Test
      public void testGetReverseRel2() {
          ObjEntity artistEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Artist");
          ObjEntity paintingEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Painting");
  
          // start with "to one"
          ObjRelationship r1 = paintingEnt.getRelationship("toArtist");
          ObjRelationship r2 = r1.getReverseRelationship();
  
          assertNotNull(r2);
          assertSame(artistEnt.getRelationship("paintingArray"), r2);
      }
  
      @Test
      public void testSingleDbRelationship() {
          ObjRelationship relationship = new ObjRelationship();
          DbRelationship r1 = new DbRelationship("X");
          relationship.addDbRelationship(r1);
          assertEquals(1, relationship.getDbRelationships().size());
          assertEquals(r1, relationship.getDbRelationships().get(0));
          assertFalse(relationship.isFlattened());
          assertFalse(relationship.isReadOnly());
          assertEquals(r1.isToMany(), relationship.isToMany());
          relationship.removeDbRelationship(r1);
          assertEquals(0, relationship.getDbRelationships().size());
      }
  
      @Test
      public void testFlattenedRelationship() {
          DbRelationship r1 = new DbRelationship("X");
          DbRelationship r2 = new DbRelationship("Y");
  
          r1.setSourceEntity(artistDBEntity);
          r1.setTargetEntityName(artistExhibitDBEntity);
          r1.setToMany(true);
  
          r2.setSourceEntity(artistExhibitDBEntity);
          r2.setTargetEntityName(exhibitDBEntity);
          r2.setToMany(false);
  
          ObjRelationship relationship = new ObjRelationship();
          relationship.addDbRelationship(r1);
          relationship.addDbRelationship(r2);
          assertTrue(relationship.isToMany());
          assertEquals(2, relationship.getDbRelationships().size());
          assertEquals(r1, relationship.getDbRelationships().get(0));
          assertEquals(r2, relationship.getDbRelationships().get(1));
  
          assertTrue(relationship.isFlattened());
  
          relationship.removeDbRelationship(r1);
          assertFalse(relationship.isToMany()); // only remaining rel is r2... a
                                                // toOne
          assertEquals(1, relationship.getDbRelationships().size());
          assertEquals(r2, relationship.getDbRelationships().get(0));
          assertFalse(relationship.isFlattened());
          assertFalse(relationship.isReadOnly());
  
      }
  
      @Test
      public void testReadOnly_Flattened1_1__N_1() {
  
          // check common vertical inheritance relationships
  
          DataMapLoader loader = runtime.getInjector().getInstance(DataMapLoader.class);
          URL url = getClass().getClassLoader().getResource("inheritance-vertical.map.xml");
          DataMap dataMap = loader.load(new URLResource(url));
          EntityResolver resolver = new EntityResolver(Collections.singleton(dataMap));
  
          ObjEntity iv2Sub1 = resolver.getObjEntity(Iv2Sub1.class);
          ObjRelationship x = iv2Sub1.getRelationship(Iv2Sub1.X.getName());
          assertTrue(x.isFlattened());
          assertFalse(x.isReadOnly());
      }
  
      @Test
      public void testReadOnlyMoreThan3DbRelsRelationship() {
          // Readonly is a flattened relationship that isn't over a single
          // many->many link
          // table
          DbRelationship r1 = new DbRelationship("X");
          DbRelationship r2 = new DbRelationship("Y");
          DbRelationship r3 = new DbRelationship("Z");
  
          r1.setSourceEntity(artistDBEntity);
          r1.setTargetEntityName(artistExhibitDBEntity);
          r1.setToMany(true);
          r2.setSourceEntity(artistExhibitDBEntity);
          r2.setTargetEntityName(exhibitDBEntity);
          r2.setToMany(false);
          r3.setSourceEntity(exhibitDBEntity);
          r3.setTargetEntityName(galleryDBEntity);
          r3.setToMany(false);
  
          ObjRelationship relationship = new ObjRelationship();
          relationship.addDbRelationship(r1);
          relationship.addDbRelationship(r2);
          relationship.addDbRelationship(r3);
  
          assertTrue(relationship.isFlattened());
          assertTrue(relationship.isToMany());
  
      }
  
      // Test for a read-only flattened relationship that is readonly because it's
      // dbrel
      // sequence is "incorrect" (or rather, unsupported)
      @Test
      public void testIncorrectSequenceReadOnlyRelationship() {
          DbRelationship r1 = new DbRelationship("X");
          DbRelationship r2 = new DbRelationship("Y");
  
          r1.setSourceEntity(artistDBEntity);
          r1.setTargetEntityName(paintingDbEntity);
          r1.setToMany(true);
          r2.setSourceEntity(paintingDbEntity);
          r2.setTargetEntityName(galleryDBEntity);
          r2.setToMany(false);
  
          ObjRelationship relationship = new ObjRelationship();
          relationship.addDbRelationship(r1);
          relationship.addDbRelationship(r2);
  
          assertTrue(relationship.isFlattened());
          assertTrue(relationship.isToMany());
      }
  
      // Test a relationship loaded from the test datamap that we know should be
      // flattened
      @Test
      public void testKnownFlattenedRelationship() {
          ObjEntity artistEnt = runtime.getDataDomain().getEntityResolver().getObjEntity("Artist");
          ObjRelationship theRel = artistEnt.getRelationship("groupArray");
          assertNotNull(theRel);
          assertTrue(theRel.isFlattened());
          assertFalse(theRel.isReadOnly());
      }
  
      @Test
      public void testBadDeleteRuleValue() {
          ObjRelationship relationship = new ObjRelationship();
  
          try {
              relationship.setDeleteRule(999);
              fail("Should have failed with IllegalArgumentException");
          } catch (IllegalArgumentException e) {
              // Good... it should throw an exception
          }
      }
  
      @Test
      public void testOkDeleteRuleValue() {
          ObjRelationship relationship = new ObjRelationship();
          try {
              relationship.setDeleteRule(DeleteRule.CASCADE);
              relationship.setDeleteRule(DeleteRule.DENY);
              relationship.setDeleteRule(DeleteRule.NULLIFY);
          } catch (IllegalArgumentException e) {
              e.printStackTrace();
              fail("Should not have thrown an exception :" + e.getMessage());
          }
      }
  }
  