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.util;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import org.apache.cayenne.PersistenceState;
  import org.apache.cayenne.Persistent;
  import org.apache.cayenne.access.DataDomain;
  import org.apache.cayenne.map.EntityResolver;
  import org.apache.cayenne.map.ObjEntity;
  import org.apache.cayenne.map.ObjRelationship;
  import org.apache.cayenne.query.RelationshipQuery;
  import org.apache.cayenne.reflect.PropertyDescriptor;
  
  /**
   * An abstract superclass of lazily faulted to-one and to-many relationships.
   * 
   * @since 1.2
   */
  public abstract class RelationshipFault<E> {
  
      protected Persistent relationshipOwner;
      protected String relationshipName;
  
      protected RelationshipFault() {
  
      }
  
      public RelationshipFault(Persistent relationshipOwner, String relationshipName) {
          if (relationshipOwner == null) {
              throw new NullPointerException("'relationshipOwner' can't be null.");
          }
  
          if (relationshipName == null) {
              throw new NullPointerException("'relationshipName' can't be null.");
          }
  
          this.relationshipOwner = relationshipOwner;
          this.relationshipName = relationshipName;
      }
  
      public String getRelationshipName() {
          return relationshipName;
      }
  
      public Persistent getRelationshipOwner() {
          return relationshipOwner;
      }
  
      protected boolean isTransientParent() {
          int state = relationshipOwner.getPersistenceState();
          return state == PersistenceState.NEW || state == PersistenceState.TRANSIENT;
      }
  
      protected boolean isUncommittedParent() {
          int state = relationshipOwner.getPersistenceState();
          return state == PersistenceState.MODIFIED || state == PersistenceState.DELETED;
      }
  
      protected abstract void mergeLocalChanges(List<E> resolved);
  
      /**
       * Executes a query that returns related objects. Subclasses would invoke
       * this method whenever they need to resolve a fault.
       */
      protected List<E> resolveFromDB() {
          // non-persistent objects shouldn't trigger a fetch
          if (isTransientParent()) {
              return new ArrayList<>();
          }
  
          /**
           * Doing that to refresh query (see CAY-2509) while resolving RelationshipFault.
           * Refreshing query is used only for parent contexts.
           */
          boolean refresh = relationshipOwner.getObjectContext().getChannel() instanceof DataDomain;
          List<E> resolved = relationshipOwner.getObjectContext().performQuery(
                  new RelationshipQuery(relationshipOwner.getObjectId(), relationshipName, refresh));
  
          /*
           * Duplicating the list (see CAY-1194). Doing that only for
           * RelationshipFault query results, so only for nested DataContexts
           */
          if (resolved instanceof RelationshipFault) {
              resolved = new ArrayList<>(resolved);
          }
  
          // merge local before updating reverse to ensure we update reverse rels
          // of the right objects (see CAY-1757)
          mergeLocalChanges(resolved);
  
          if (resolved.isEmpty()) {
              return resolved;
          }
  
          updateReverse(resolved);
  
          return resolved;
      }
  
      // see if reverse relationship is to-one and we can connect source to
      // results....
      protected void updateReverse(List<E> resolved) {
          EntityResolver resolver = relationshipOwner.getObjectContext().getEntityResolver();
          ObjEntity sourceEntity = resolver.getObjEntity(relationshipOwner.getObjectId().getEntityName());
  
          ObjRelationship relationship = sourceEntity.getRelationship(relationshipName);
          ObjRelationship reverse = relationship.getReverseRelationship();
  
          if (reverse != null && !reverse.isToMany()) {
              PropertyDescriptor property = resolver.getClassDescriptor(reverse.getSourceEntity().getName()).getProperty(
                      reverse.getName());
  
              for (Object o : resolved) {
                  property.writePropertyDirectly(o, null, relationshipOwner);
              }
          }
      }
  }
  