001package io.ebeaninternal.server.autotune.service;
002
003import io.ebean.bean.NodeUsageCollector;
004import io.ebean.text.PathProperties;
005import io.ebean.util.SplitName;
006import io.ebeaninternal.server.deploy.BeanDescriptor;
007import io.ebeaninternal.server.deploy.BeanProperty;
008import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
009import io.ebeaninternal.server.el.ElPropertyValue;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import java.util.LinkedHashSet;
014import java.util.Set;
015import java.util.concurrent.locks.ReentrantLock;
016
017/**
018 * Collects usages statistics for a given node in the object graph.
019 */
020public class ProfileOriginNodeUsage {
021
022  private static final Logger logger = LoggerFactory.getLogger(ProfileOriginNodeUsage.class);
023
024  private final ReentrantLock lock = new ReentrantLock();
025
026  private final String path;
027
028  private int profileCount;
029
030  private int profileUsedCount;
031
032  private boolean modified;
033
034  private final Set<String> aggregateUsed = new LinkedHashSet<>();
035
036  public ProfileOriginNodeUsage(String path) {
037    // handle null paths as using ConcurrentHashMap
038    this.path = "".equals(path) ? null : path;
039  }
040
041  protected void buildTunedFetch(PathProperties pathProps, BeanDescriptor<?> rootDesc, boolean addVersionProperty) {
042    lock.lock();
043    try {
044      BeanDescriptor<?> desc = rootDesc;
045      if (path != null) {
046        ElPropertyValue elGetValue = rootDesc.getElGetValue(path);
047        if (elGetValue == null) {
048          logger.warn("AutoTune: Can't find join for path[" + path + "] for " + rootDesc.getName());
049          return;
050        } else {
051          BeanProperty beanProperty = elGetValue.getBeanProperty();
052          if (beanProperty instanceof BeanPropertyAssoc<?>) {
053            desc = ((BeanPropertyAssoc<?>) beanProperty).getTargetDescriptor();
054          }
055        }
056      }
057
058      BeanProperty toOneIdProperty = null;
059      boolean addedToPath = false;
060
061      for (String propName : aggregateUsed) {
062        BeanProperty beanProp = desc.findPropertyFromPath(propName);
063        if (beanProp == null) {
064          logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
065
066        } else {
067          if (beanProp.isId()) {
068            // remember and maybe add ToOne property to parent path
069            toOneIdProperty = beanProp;
070          } else if (beanProp instanceof BeanPropertyAssoc<?>) {
071            // intentionally skip
072          } else {
073            //noinspection StatementWithEmptyBody
074            if (beanProp.isLob() && !beanProp.isFetchEager()) {
075              // AutoTune will not include Lob's marked FetchLazy
076              // (which is the default for Lob's so typical).
077            } else {
078              addedToPath = true;
079              pathProps.addToPath(path, beanProp.getName());
080            }
081          }
082        }
083      }
084
085      if ((modified || addVersionProperty) && desc != null) {
086        BeanProperty versionProp = desc.getVersionProperty();
087        if (versionProp != null) {
088          addedToPath = true;
089          pathProps.addToPath(path, versionProp.getName());
090        }
091      }
092
093      if (toOneIdProperty != null && !addedToPath) {
094        // add ToOne property to parent path
095        ElPropertyValue assocOne = rootDesc.getElGetValue(path);
096        pathProps.addToPath(SplitName.parent(path), assocOne.getName());
097      }
098    } finally {
099      lock.unlock();
100    }
101  }
102
103  /**
104   * Collect usage from a node.
105   */
106  protected void collectUsageInfo(NodeUsageCollector profile) {
107    lock.lock();
108    try {
109      Set<String> used = profile.getUsed();
110
111      profileCount++;
112      if (!used.isEmpty()) {
113        profileUsedCount++;
114        aggregateUsed.addAll(used);
115      }
116      if (profile.isModified()) {
117        modified = true;
118      }
119    } finally {
120      lock.unlock();
121    }
122  }
123
124  @Override
125  public String toString() {
126    return "path[" + path + "] profileCount[" + profileCount + "] used[" + profileUsedCount + "] props" + aggregateUsed;
127  }
128}