001package io.ebeanservice.docstore.api.support;
002
003import io.ebean.FetchPath;
004import io.ebean.Query;
005import io.ebean.annotation.DocStore;
006import io.ebean.annotation.DocStoreMode;
007import io.ebean.docstore.DocUpdateContext;
008import io.ebean.plugin.BeanType;
009import io.ebean.text.PathProperties;
010import io.ebeaninternal.api.SpiEbeanServer;
011import io.ebeaninternal.server.core.PersistRequest;
012import io.ebeaninternal.server.core.PersistRequestBean;
013import io.ebeaninternal.server.deploy.BeanDescriptor;
014import io.ebeaninternal.server.deploy.BeanProperty;
015import io.ebeaninternal.server.deploy.InheritInfo;
016import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
017import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
018import io.ebeanservice.docstore.api.DocStoreUpdateContext;
019import io.ebeanservice.docstore.api.DocStoreUpdates;
020import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
021import io.ebeanservice.docstore.api.mapping.DocumentMapping;
022
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030/**
031 * Base implementation for much of DocStoreBeanAdapter.
032 */
033public abstract class DocStoreBeanBaseAdapter<T> implements DocStoreBeanAdapter<T> {
034
035  protected final SpiEbeanServer server;
036
037  /**
038   * The associated BeanDescriptor.
039   */
040  protected final BeanDescriptor<T> desc;
041
042  /**
043   * The type of index.
044   */
045  protected final boolean mapped;
046
047  /**
048   * Identifier used in the queue system to identify the index.
049   */
050  protected final String queueId;
051
052  /**
053   * ElasticSearch index type.
054   */
055  protected final String indexType;
056
057  /**
058   * ElasticSearch index name.
059   */
060  protected final String indexName;
061
062  /**
063   * Doc store deployment annotation.
064   */
065  private final DocStore docStore;
066
067  /**
068   * Behavior on insert.
069   */
070  protected final DocStoreMode insert;
071
072  /**
073   * Behavior on update.
074   */
075  protected DocStoreMode update;
076
077  /**
078   * Behavior on delete.
079   */
080  protected final DocStoreMode delete;
081
082  /**
083   * List of embedded paths from other documents that include this document type.
084   * As such an update to this doc type means that those embedded documents need to be updated.
085   */
086  protected final List<DocStoreEmbeddedInvalidation> embeddedInvalidation = new ArrayList<>();
087
088  protected final PathProperties pathProps;
089
090  /**
091   * Map of properties to 'raw' properties.
092   */
093  protected Map<String, String> sortableMap;
094
095  /**
096   * Nested path properties defining the doc structure for indexing.
097   */
098  protected DocStructure docStructure;
099
100  protected DocumentMapping documentMapping;
101
102  private boolean registerPaths;
103
104  public DocStoreBeanBaseAdapter(BeanDescriptor<T> desc, DeployBeanDescriptor<T> deploy) {
105    this.desc = desc;
106    this.server = desc.ebeanServer();
107    this.mapped = deploy.isDocStoreMapped();
108    this.pathProps = deploy.getDocStorePathProperties();
109    this.docStore = deploy.getDocStore();
110    this.queueId = derive(desc, deploy.getDocStoreQueueId());
111    this.indexName = derive(desc, deploy.getDocStoreIndexName());
112    this.indexType = derive(desc, deploy.getDocStoreIndexType());
113    this.insert = deploy.getDocStoreInsertEvent();
114    this.update = deploy.getDocStoreUpdateEvent();
115    this.delete = deploy.getDocStoreDeleteEvent();
116  }
117
118  @Override
119  public boolean hasEmbeddedInvalidation() {
120    return !embeddedInvalidation.isEmpty();
121  }
122
123  @Override
124  public DocumentMapping createDocMapping() {
125    if (documentMapping != null) {
126      return documentMapping;
127    }
128
129    if (!mapped) return null;
130
131    this.docStructure = derivePathProperties(pathProps);
132
133    DocMappingBuilder mappingBuilder = new DocMappingBuilder(docStructure.doc(), docStore);
134    desc.docStoreMapping(mappingBuilder, null);
135    mappingBuilder.applyMapping();
136
137    sortableMap = mappingBuilder.collectSortable();
138    docStructure.prepareMany(desc);
139    documentMapping = mappingBuilder.create(queueId, indexName, indexType);
140    return documentMapping;
141  }
142
143  @Override
144  public String indexType() {
145    return indexType;
146  }
147
148  @Override
149  public String indexName() {
150    return indexName;
151  }
152
153  @Override
154  public void applyPath(Query<T> query) {
155    query.apply(docStructure.doc());
156  }
157
158  @Override
159  public String rawProperty(String property) {
160    String rawProperty = sortableMap.get(property);
161    return rawProperty == null ? property : rawProperty;
162  }
163
164  /**
165   * Register invalidation paths for embedded documents.
166   */
167  @Override
168  public void registerPaths() {
169    if (mapped && !registerPaths) {
170      Collection<PathProperties.Props> pathProps = docStructure.doc().getPathProps();
171      for (PathProperties.Props pathProp : pathProps) {
172        String path = pathProp.getPath();
173        if (path != null) {
174          BeanDescriptor<?> targetDesc = desc.descriptor(path);
175          BeanProperty idProperty = targetDesc.idProperty();
176          if (idProperty != null) {
177            // embedded beans don't have id property
178            String fullPath = path + "." + idProperty.name();
179            targetDesc.docStoreAdapter().registerInvalidationPath(desc.docStoreQueueId(), fullPath, pathProp.getProperties());
180          }
181        }
182      }
183      registerPaths = true;
184    }
185  }
186
187  /**
188   * Register a doc store invalidation listener for the given bean type, path and properties.
189   */
190  @Override
191  public void registerInvalidationPath(String queueId, String path, Set<String> properties) {
192    if (!mapped) {
193      if (update == DocStoreMode.IGNORE) {
194        // bean type not mapped but is included as nested document
195        // in a doc store index so we need to update
196        update = DocStoreMode.UPDATE;
197      }
198    }
199    embeddedInvalidation.add(embeddedInvalidation(queueId, path, properties));
200  }
201
202  /**
203   * Return the DsInvalidationListener based on the properties, path.
204   */
205  protected DocStoreEmbeddedInvalidation embeddedInvalidation(String queueId, String path, Set<String> properties) {
206    if (properties.contains("*")) {
207      return new DocStoreEmbeddedInvalidation(queueId, path);
208    } else {
209      return new DocStoreEmbeddedInvalidationProperties(queueId, path, propertyPositions(properties));
210    }
211  }
212
213  /**
214   * Return the property names as property index positions.
215   */
216  protected int[] propertyPositions(Set<String> properties) {
217    List<Integer> posList = new ArrayList<>();
218    for (String property : properties) {
219      BeanProperty prop = desc.beanProperty(property);
220      if (prop != null) {
221        posList.add(prop.propertyIndex());
222      }
223    }
224    int[] pos = new int[posList.size()];
225    for (int i = 0; i < pos.length; i++) {
226      pos[i] = posList.get(i);
227    }
228    return pos;
229  }
230
231  @Override
232  public void updateEmbedded(PersistRequestBean<T> request, DocStoreUpdates docStoreUpdates) {
233    for (DocStoreEmbeddedInvalidation anEmbeddedInvalidation : embeddedInvalidation) {
234      anEmbeddedInvalidation.embeddedInvalidate(request, docStoreUpdates);
235    }
236  }
237
238  /**
239   * Return the pathProperties which defines the JSON document to index.
240   * This can add derived/embedded/nested parts to the document.
241   */
242  protected DocStructure derivePathProperties(PathProperties pathProps) {
243    boolean includeByDefault = (pathProps == null);
244    if (pathProps == null) {
245      pathProps = new PathProperties();
246    }
247    return docStructure(pathProps, includeByDefault);
248  }
249
250  protected DocStructure docStructure(PathProperties pathProps, final boolean includeByDefault) {
251    final DocStructure docStructure = new DocStructure(pathProps);
252    BeanProperty[] properties = desc.propertiesNonTransient();
253    for (BeanProperty property : properties) {
254      property.docStoreInclude(includeByDefault, docStructure);
255    }
256
257    InheritInfo inheritInfo = desc.inheritInfo();
258    if (inheritInfo != null) {
259      inheritInfo.visitChildren(inheritInfo1 -> {
260        for (BeanProperty localProperty : inheritInfo1.localProperties()) {
261          localProperty.docStoreInclude(includeByDefault, docStructure);
262        }
263      });
264    }
265    return docStructure;
266  }
267
268  @Override
269  public FetchPath embedded(String path) {
270    return docStructure.embedded(path);
271  }
272
273  @Override
274  public FetchPath embeddedManyRoot(String path) {
275    return docStructure.embeddedManyRoot(path);
276  }
277
278  @Override
279  public boolean mapped() {
280    return mapped;
281  }
282
283  @Override
284  public String queueId() {
285    return queueId;
286  }
287
288  @Override
289  public DocStoreMode mode(PersistRequest.Type persistType, DocStoreMode txnMode) {
290    if (txnMode == null) {
291      return mode(persistType);
292    } else if (txnMode == DocStoreMode.IGNORE) {
293      return DocStoreMode.IGNORE;
294    }
295    return mapped ? txnMode : mode(persistType);
296  }
297
298  private DocStoreMode mode(PersistRequest.Type persistType) {
299    switch (persistType) {
300      case INSERT:
301        return insert;
302      case UPDATE:
303        return update;
304      case DELETE:
305        return delete;
306      default:
307        return DocStoreMode.IGNORE;
308    }
309  }
310
311  /**
312   * Return the supplied value or default to the bean name lower case.
313   */
314  protected String derive(BeanType<?> desc, String suppliedValue) {
315    return (suppliedValue != null && !suppliedValue.isEmpty()) ? suppliedValue : desc.name().toLowerCase();
316  }
317
318  @Override
319  public abstract void deleteById(Object idValue, DocUpdateContext txn) throws IOException;
320
321  @Override
322  public abstract void index(Object idValue, T entityBean, DocUpdateContext txn) throws IOException;
323
324  @Override
325  public abstract void insert(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext txn) throws IOException;
326
327  @Override
328  public abstract void update(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext txn) throws IOException;
329
330  @Override
331  public abstract void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocUpdateContext txn) throws IOException;
332
333}