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
106    this.desc = desc;
107    this.server = desc.getEbeanServer();
108    this.mapped = deploy.isDocStoreMapped();
109    this.pathProps = deploy.getDocStorePathProperties();
110    this.docStore = deploy.getDocStore();
111    this.queueId = derive(desc, deploy.getDocStoreQueueId());
112    this.indexName = derive(desc, deploy.getDocStoreIndexName());
113    this.indexType = derive(desc, deploy.getDocStoreIndexType());
114    this.insert = deploy.getDocStoreInsertEvent();
115    this.update = deploy.getDocStoreUpdateEvent();
116    this.delete = deploy.getDocStoreDeleteEvent();
117  }
118
119  @Override
120  public boolean hasEmbeddedInvalidation() {
121    return !embeddedInvalidation.isEmpty();
122  }
123
124  @Override
125  public DocumentMapping createDocMapping() {
126
127    if (documentMapping != null) {
128      return documentMapping;
129    }
130
131    if (!mapped) return null;
132
133    this.docStructure = derivePathProperties(pathProps);
134
135    DocMappingBuilder mappingBuilder = new DocMappingBuilder(docStructure.doc(), docStore);
136    desc.docStoreMapping(mappingBuilder, null);
137    mappingBuilder.applyMapping();
138
139    sortableMap = mappingBuilder.collectSortable();
140    docStructure.prepareMany(desc);
141    documentMapping = mappingBuilder.create(queueId, indexName, indexType);
142    return documentMapping;
143  }
144
145  @Override
146  public String getIndexType() {
147    return indexType;
148  }
149
150  @Override
151  public String getIndexName() {
152    return indexName;
153  }
154
155  @Override
156  public void applyPath(Query<T> query) {
157    query.apply(docStructure.doc());
158  }
159
160  @Override
161  public String rawProperty(String property) {
162
163    String rawProperty = sortableMap.get(property);
164    return rawProperty == null ? property : rawProperty;
165  }
166
167  /**
168   * Register invalidation paths for embedded documents.
169   */
170  @Override
171  public void registerPaths() {
172    if (mapped && !registerPaths) {
173      Collection<PathProperties.Props> pathProps = docStructure.doc().getPathProps();
174      for (PathProperties.Props pathProp : pathProps) {
175        String path = pathProp.getPath();
176        if (path != null) {
177          BeanDescriptor<?> targetDesc = desc.getBeanDescriptor(path);
178          BeanProperty idProperty = targetDesc.getIdProperty();
179          if (idProperty != null) {
180            // embedded beans don't have id property
181            String fullPath = path + "." + idProperty.getName();
182            targetDesc.docStoreAdapter().registerInvalidationPath(desc.getDocStoreQueueId(), fullPath, pathProp.getProperties());
183          }
184        }
185      }
186      registerPaths = true;
187    }
188  }
189
190  /**
191   * Register a doc store invalidation listener for the given bean type, path and properties.
192   */
193  @Override
194  public void registerInvalidationPath(String queueId, String path, Set<String> properties) {
195
196    if (!mapped) {
197      if (update == DocStoreMode.IGNORE) {
198        // bean type not mapped but is included as nested document
199        // in a doc store index so we need to update
200        update = DocStoreMode.UPDATE;
201      }
202    }
203    embeddedInvalidation.add(getEmbeddedInvalidation(queueId, path, properties));
204  }
205
206  /**
207   * Return the DsInvalidationListener based on the properties, path.
208   */
209  protected DocStoreEmbeddedInvalidation getEmbeddedInvalidation(String queueId, String path, Set<String> properties) {
210
211    if (properties.contains("*")) {
212      return new DocStoreEmbeddedInvalidation(queueId, path);
213    } else {
214      return new DocStoreEmbeddedInvalidationProperties(queueId, path, getPropertyPositions(properties));
215    }
216  }
217
218  /**
219   * Return the property names as property index positions.
220   */
221  protected int[] getPropertyPositions(Set<String> properties) {
222    List<Integer> posList = new ArrayList<>();
223    for (String property : properties) {
224      BeanProperty prop = desc.getBeanProperty(property);
225      if (prop != null) {
226        posList.add(prop.getPropertyIndex());
227      }
228    }
229    int[] pos = new int[posList.size()];
230    for (int i = 0; i < pos.length; i++) {
231      pos[i] = posList.get(i);
232    }
233    return pos;
234  }
235
236  @Override
237  public void updateEmbedded(PersistRequestBean<T> request, DocStoreUpdates docStoreUpdates) {
238    for (DocStoreEmbeddedInvalidation anEmbeddedInvalidation : embeddedInvalidation) {
239      anEmbeddedInvalidation.embeddedInvalidate(request, docStoreUpdates);
240    }
241  }
242
243  /**
244   * Return the pathProperties which defines the JSON document to index.
245   * This can add derived/embedded/nested parts to the document.
246   */
247  protected DocStructure derivePathProperties(PathProperties pathProps) {
248
249    boolean includeByDefault = (pathProps == null);
250    if (pathProps == null) {
251      pathProps = new PathProperties();
252    }
253
254    return getDocStructure(pathProps, includeByDefault);
255  }
256
257  protected DocStructure getDocStructure(PathProperties pathProps, final boolean includeByDefault) {
258
259    final DocStructure docStructure = new DocStructure(pathProps);
260
261    BeanProperty[] properties = desc.propertiesNonTransient();
262    for (BeanProperty property : properties) {
263      property.docStoreInclude(includeByDefault, docStructure);
264    }
265
266    InheritInfo inheritInfo = desc.getInheritInfo();
267    if (inheritInfo != null) {
268      inheritInfo.visitChildren(inheritInfo1 -> {
269        for (BeanProperty localProperty : inheritInfo1.localProperties()) {
270          localProperty.docStoreInclude(includeByDefault, docStructure);
271        }
272      });
273    }
274
275    return docStructure;
276  }
277
278  @Override
279  public FetchPath getEmbedded(String path) {
280    return docStructure.getEmbedded(path);
281  }
282
283  @Override
284  public FetchPath getEmbeddedManyRoot(String path) {
285    return docStructure.getEmbeddedManyRoot(path);
286  }
287
288  @Override
289  public boolean isMapped() {
290    return mapped;
291  }
292
293  @Override
294  public String getQueueId() {
295    return queueId;
296  }
297
298  @Override
299  public DocStoreMode getMode(PersistRequest.Type persistType, DocStoreMode txnMode) {
300
301    if (txnMode == null) {
302      return getMode(persistType);
303    } else if (txnMode == DocStoreMode.IGNORE) {
304      return DocStoreMode.IGNORE;
305    }
306    return mapped ? txnMode : getMode(persistType);
307  }
308
309  private DocStoreMode getMode(PersistRequest.Type persistType) {
310    switch (persistType) {
311      case INSERT:
312        return insert;
313      case UPDATE:
314        return update;
315      case DELETE:
316        return delete;
317      default:
318        return DocStoreMode.IGNORE;
319    }
320  }
321
322  /**
323   * Return the supplied value or default to the bean name lower case.
324   */
325  protected String derive(BeanType<?> desc, String suppliedValue) {
326    return (suppliedValue != null && !suppliedValue.isEmpty()) ? suppliedValue : desc.getName().toLowerCase();
327  }
328
329  @Override
330  public abstract void deleteById(Object idValue, DocUpdateContext txn) throws IOException;
331
332  @Override
333  public abstract void index(Object idValue, T entityBean, DocUpdateContext txn) throws IOException;
334
335  @Override
336  public abstract void insert(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext txn) throws IOException;
337
338  @Override
339  public abstract void update(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext txn) throws IOException;
340
341  @Override
342  public abstract void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocUpdateContext txn) throws IOException;
343
344}