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