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}