001package com.avaje.ebeanservice.docstore.api.support;
002
003import com.avaje.ebean.FetchPath;
004import com.avaje.ebean.Query;
005import com.avaje.ebean.annotation.DocStore;
006import com.avaje.ebean.annotation.DocStoreMode;
007import com.avaje.ebean.plugin.BeanType;
008import com.avaje.ebean.text.PathProperties;
009import com.avaje.ebeaninternal.api.SpiEbeanServer;
010import com.avaje.ebeaninternal.server.core.PersistRequest;
011import com.avaje.ebeaninternal.server.core.PersistRequestBean;
012import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
013import com.avaje.ebeaninternal.server.deploy.BeanProperty;
014import com.avaje.ebeaninternal.server.deploy.InheritInfo;
015import com.avaje.ebeaninternal.server.deploy.InheritInfoVisitor;
016import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
017import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
018import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
019import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
020import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
021import com.avaje.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<DocStoreEmbeddedInvalidation>();
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          String idName = targetDesc.getIdProperty().getName();
179          String fullPath = path + "." + idName;
180          targetDesc.docStoreAdapter().registerInvalidationPath(desc.getDocStoreQueueId(), fullPath, pathProp.getProperties());
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
193    if (!mapped) {
194      if (update == DocStoreMode.IGNORE) {
195        // bean type not mapped but is included as nested document
196        // in a doc store index so we need to update
197        update = DocStoreMode.UPDATE;
198      }
199    }
200    embeddedInvalidation.add(getEmbeddedInvalidation(queueId, path, properties));
201  }
202
203  /**
204   * Return the DsInvalidationListener based on the properties, path.
205   */
206  protected DocStoreEmbeddedInvalidation getEmbeddedInvalidation(String queueId, String path, Set<String> properties) {
207
208    if (properties.contains("*")) {
209      return new DocStoreEmbeddedInvalidation(queueId, path);
210    } else {
211      return new DocStoreEmbeddedInvalidationProperties(queueId, path, getPropertyPositions(properties));
212    }
213  }
214
215  /**
216   * Return the property names as property index positions.
217   */
218  protected int[] getPropertyPositions(Set<String> properties) {
219    List<Integer> posList = new ArrayList<Integer>();
220    for (String property : properties) {
221      BeanProperty prop = desc.getBeanProperty(property);
222      if (prop != null) {
223        posList.add(prop.getPropertyIndex());
224      }
225    }
226    int[] pos = new int[posList.size()];
227    for (int i = 0; i <pos.length; i++) {
228      pos[i] = posList.get(i);
229    }
230    return pos;
231  }
232
233  @Override
234  public void updateEmbedded(PersistRequestBean<T> request, DocStoreUpdates docStoreUpdates) {
235    for (int i = 0; i < embeddedInvalidation.size(); i++) {
236      embeddedInvalidation.get(i).embeddedInvalidate(request, docStoreUpdates);
237    }
238  }
239
240  /**
241   * Return the pathProperties which defines the JSON document to index.
242   * This can add derived/embedded/nested parts to the document.
243   */
244  protected DocStructure derivePathProperties(PathProperties pathProps) {
245
246    boolean includeByDefault = (pathProps == null);
247    if (pathProps  == null) {
248      pathProps = new PathProperties();
249    }
250
251    return getDocStructure(pathProps, includeByDefault);
252  }
253
254  protected DocStructure getDocStructure(PathProperties pathProps, final boolean includeByDefault) {
255
256    final DocStructure docStructure = new DocStructure(pathProps);
257
258    BeanProperty[] properties = desc.propertiesNonTransient();
259    for (int i = 0; i < properties.length; i++) {
260      properties[i].docStoreInclude(includeByDefault, docStructure);
261    }
262
263    InheritInfo inheritInfo = desc.getInheritInfo();
264    if (inheritInfo != null) {
265      inheritInfo.visitChildren(new InheritInfoVisitor() {
266        @Override
267        public void visit(InheritInfo inheritInfo) {
268          for (BeanProperty localProperty : inheritInfo.localProperties()) {
269            localProperty.docStoreInclude(includeByDefault, docStructure);
270          }
271        }
272      });
273    }
274
275    return docStructure;
276  }
277
278  public FetchPath getEmbedded(String path) {
279    return docStructure.getEmbedded(path);
280  }
281
282  public FetchPath getEmbeddedManyRoot(String path) {
283    return docStructure.getEmbeddedManyRoot(path);
284  }
285
286  @Override
287  public boolean isMapped() {
288    return mapped;
289  }
290
291  @Override
292  public String getQueueId() {
293    return queueId;
294  }
295
296  @Override
297  public DocStoreMode getMode(PersistRequest.Type persistType, DocStoreMode txnMode) {
298
299    if (txnMode == null) {
300      return getMode(persistType);
301    } else if (txnMode == DocStoreMode.IGNORE) {
302      return DocStoreMode.IGNORE;
303    }
304    return mapped ? txnMode : getMode(persistType);
305  }
306
307  private DocStoreMode getMode(PersistRequest.Type persistType) {
308    switch (persistType) {
309      case INSERT:
310        return insert;
311      case UPDATE:
312        return update;
313      case DELETE:
314        return delete;
315      default:
316        return DocStoreMode.IGNORE;
317    }
318  }
319
320  /**
321   * Return the supplied value or default to the bean name lower case.
322   */
323  protected String derive(BeanType<?> desc, String suppliedValue) {
324    return (suppliedValue != null && !suppliedValue.isEmpty()) ? suppliedValue : desc.getName().toLowerCase();
325  }
326
327  @Override
328  public abstract void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException;
329
330  @Override
331  public abstract void index(Object idValue, T entityBean, DocStoreUpdateContext txn) throws IOException;
332
333  @Override
334  public abstract void insert(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext txn) throws IOException;
335
336  @Override
337  public abstract void update(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext txn) throws IOException;
338
339  @Override
340  public abstract void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException;
341
342}