PageRenderTime 27ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/astyanax-entity-mapper/src/main/java/com/netflix/astyanax/entitystore/CompositeColumnEntityMapper.java

http://github.com/Netflix/astyanax
Java | 360 lines | 215 code | 47 blank | 98 comment | 30 complexity | ef7f6a5dfa99fb6e90b9c637ba1b87c0 MD5 | raw file
  1. /**
  2. * Copyright 2013 Netflix, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.netflix.astyanax.entitystore;
  17. import java.lang.reflect.Field;
  18. import java.lang.reflect.ParameterizedType;
  19. import java.nio.ByteBuffer;
  20. import java.util.Collection;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import java.util.Set;
  24. import javax.persistence.Column;
  25. import javax.persistence.PersistenceException;
  26. import org.apache.commons.lang.StringUtils;
  27. import com.google.common.base.Function;
  28. import com.google.common.base.Preconditions;
  29. import com.google.common.collect.ArrayListMultimap;
  30. import com.google.common.collect.Collections2;
  31. import com.google.common.collect.Lists;
  32. import com.google.common.collect.Sets;
  33. import com.netflix.astyanax.ColumnListMutation;
  34. import com.netflix.astyanax.model.ColumnList;
  35. import com.netflix.astyanax.model.Equality;
  36. import com.netflix.astyanax.query.ColumnPredicate;
  37. /**
  38. * Mapper from a CompositeType to an embedded entity. The composite entity is expected
  39. * to have an @Id annotation for each composite component and a @Column annotation for
  40. * the value.
  41. *
  42. * @author elandau
  43. *
  44. */
  45. public class CompositeColumnEntityMapper {
  46. /**
  47. * Class of embedded entity
  48. */
  49. private final Class<?> clazz;
  50. /**
  51. * List of serializers for the composite parts
  52. */
  53. private List<FieldMapper<?>> components = Lists.newArrayList();
  54. /**
  55. * List of valid (i.e. existing) column names
  56. */
  57. private Set<String> validNames = Sets.newHashSet();
  58. /**
  59. * Mapper for the value part of the entity
  60. */
  61. private FieldMapper<?> valueMapper;
  62. /**
  63. * Largest buffer size
  64. */
  65. private int bufferSize = 64;
  66. /**
  67. * Parent field
  68. */
  69. private final Field containerField;
  70. public CompositeColumnEntityMapper(Field field) {
  71. ParameterizedType containerEntityType = (ParameterizedType) field.getGenericType();
  72. this.clazz = (Class<?>) containerEntityType.getActualTypeArguments()[0];
  73. this.containerField = field;
  74. this.containerField.setAccessible(true);
  75. Field[] declaredFields = clazz.getDeclaredFields();
  76. for (Field f : declaredFields) {
  77. // The value
  78. Column columnAnnotation = f.getAnnotation(Column.class);
  79. if ((columnAnnotation != null)) {
  80. f.setAccessible(true);
  81. FieldMapper fieldMapper = new FieldMapper(f);
  82. components.add(fieldMapper);
  83. validNames.add(fieldMapper.getName());
  84. }
  85. }
  86. // Last one is always treated as the 'value'
  87. valueMapper = components.remove(components.size() - 1);
  88. }
  89. /**
  90. * Iterate through the list and create a column for each element
  91. * @param clm
  92. * @param entity
  93. * @throws IllegalArgumentException
  94. * @throws IllegalAccessException
  95. */
  96. public void fillMutationBatch(ColumnListMutation<ByteBuffer> clm, Object entity) throws IllegalArgumentException, IllegalAccessException {
  97. List<?> list = (List<?>) containerField.get(entity);
  98. if (list != null) {
  99. for (Object element : list) {
  100. fillColumnMutation(clm, element);
  101. }
  102. }
  103. }
  104. public void fillMutationBatchForDelete(ColumnListMutation<ByteBuffer> clm, Object entity) throws IllegalArgumentException, IllegalAccessException {
  105. List<?> list = (List<?>) containerField.get(entity);
  106. if (list == null) {
  107. clm.delete();
  108. }
  109. else {
  110. for (Object element : list) {
  111. clm.deleteColumn(toColumnName(element));
  112. }
  113. }
  114. }
  115. /**
  116. * Add a column based on the provided entity
  117. *
  118. * @param clm
  119. * @param entity
  120. */
  121. public void fillColumnMutation(ColumnListMutation<ByteBuffer> clm, Object entity) {
  122. try {
  123. ByteBuffer columnName = toColumnName(entity);
  124. ByteBuffer value = valueMapper.toByteBuffer(entity);
  125. clm.putColumn(columnName, value);
  126. } catch(Exception e) {
  127. throw new PersistenceException("failed to fill mutation batch", e);
  128. }
  129. }
  130. /**
  131. * Return the column name byte buffer for this entity
  132. *
  133. * @param obj
  134. * @return
  135. */
  136. public ByteBuffer toColumnName(Object obj) {
  137. SimpleCompositeBuilder composite = new SimpleCompositeBuilder(bufferSize, Equality.EQUAL);
  138. // Iterate through each component and add to a CompositeType structure
  139. for (FieldMapper<?> mapper : components) {
  140. try {
  141. composite.addWithoutControl(mapper.toByteBuffer(obj));
  142. }
  143. catch (Exception e) {
  144. throw new RuntimeException(e);
  145. }
  146. }
  147. return composite.get();
  148. }
  149. /**
  150. * Set the collection field using the provided column list of embedded entities
  151. * @param entity
  152. * @param name
  153. * @param column
  154. * @return
  155. * @throws Exception
  156. */
  157. public boolean setField(Object entity, ColumnList<ByteBuffer> columns) throws Exception {
  158. List<Object> list = getOrCreateField(entity);
  159. // Iterate through columns and add embedded entities to the list
  160. for (com.netflix.astyanax.model.Column<ByteBuffer> c : columns) {
  161. list.add(fromColumn(c));
  162. }
  163. return true;
  164. }
  165. public boolean setFieldFromCql(Object entity, ColumnList<ByteBuffer> columns) throws Exception {
  166. List<Object> list = getOrCreateField(entity);
  167. // Iterate through columns and add embedded entities to the list
  168. // for (com.netflix.astyanax.model.Column<ByteBuffer> c : columns) {
  169. list.add(fromCqlColumns(columns));
  170. // }
  171. return true;
  172. }
  173. private List<Object> getOrCreateField(Object entity) throws IllegalArgumentException, IllegalAccessException {
  174. // Get or create the list field
  175. List<Object> list = (List<Object>) containerField.get(entity);
  176. if (list == null) {
  177. list = Lists.newArrayList();
  178. containerField.set(entity, list);
  179. }
  180. return list;
  181. }
  182. /**
  183. * Return an object from the column
  184. *
  185. * @param cl
  186. * @return
  187. */
  188. public Object fromColumn(com.netflix.astyanax.model.Column<ByteBuffer> c) {
  189. try {
  190. // Allocate a new entity
  191. Object entity = clazz.newInstance();
  192. setEntityFieldsFromColumnName(entity, c.getRawName().duplicate());
  193. valueMapper.setField(entity, c.getByteBufferValue().duplicate());
  194. return entity;
  195. } catch(Exception e) {
  196. throw new PersistenceException("failed to construct entity", e);
  197. }
  198. }
  199. public Object fromCqlColumns(com.netflix.astyanax.model.ColumnList<ByteBuffer> c) {
  200. try {
  201. // Allocate a new entity
  202. Object entity = clazz.newInstance();
  203. Iterator<com.netflix.astyanax.model.Column<ByteBuffer>> columnIter = c.iterator();
  204. columnIter.next();
  205. for (FieldMapper<?> component : components) {
  206. component.setField(entity, columnIter.next().getByteBufferValue());
  207. }
  208. valueMapper.setField(entity, columnIter.next().getByteBufferValue());
  209. return entity;
  210. } catch(Exception e) {
  211. throw new PersistenceException("failed to construct entity", e);
  212. }
  213. }
  214. /**
  215. *
  216. * @param entity
  217. * @param columnName
  218. * @throws IllegalArgumentException
  219. * @throws IllegalAccessException
  220. */
  221. public void setEntityFieldsFromColumnName(Object entity, ByteBuffer columnName) throws IllegalArgumentException, IllegalAccessException {
  222. // Iterate through components in order and set fields
  223. for (FieldMapper<?> component : components) {
  224. ByteBuffer data = getWithShortLength(columnName);
  225. if (data != null) {
  226. if (data.remaining() > 0) {
  227. component.setField(entity, data);
  228. }
  229. byte end_of_component = columnName.get();
  230. if (end_of_component != Equality.EQUAL.toByte()) {
  231. throw new RuntimeException("Invalid composite column. Expected END_OF_COMPONENT.");
  232. }
  233. }
  234. else {
  235. throw new RuntimeException("Missing component data in composite type");
  236. }
  237. }
  238. }
  239. /**
  240. * Return the cassandra comparator type for this composite structure
  241. * @return
  242. */
  243. public String getComparatorType() {
  244. StringBuilder sb = new StringBuilder();
  245. sb.append("CompositeType(");
  246. sb.append(StringUtils.join(
  247. Collections2.transform(components, new Function<FieldMapper<?>, String>() {
  248. public String apply(FieldMapper<?> input) {
  249. return input.serializer.getComparatorType().getClassName();
  250. }
  251. }),
  252. ","));
  253. sb.append(")");
  254. return sb.toString();
  255. }
  256. public static int getShortLength(ByteBuffer bb) {
  257. int length = (bb.get() & 0xFF) << 8;
  258. return length | (bb.get() & 0xFF);
  259. }
  260. public static ByteBuffer getWithShortLength(ByteBuffer bb) {
  261. int length = getShortLength(bb);
  262. return getBytes(bb, length);
  263. }
  264. public static ByteBuffer getBytes(ByteBuffer bb, int length) {
  265. ByteBuffer copy = bb.duplicate();
  266. copy.limit(copy.position() + length);
  267. bb.position(bb.position() + length);
  268. return copy;
  269. }
  270. public String getValueType() {
  271. return valueMapper.getSerializer().getComparatorType().getClassName();
  272. }
  273. public ByteBuffer[] getQueryEndpoints(Collection<ColumnPredicate> predicates) {
  274. // Convert to multimap for easy lookup
  275. ArrayListMultimap<Object, ColumnPredicate> lookup = ArrayListMultimap.create();
  276. for (ColumnPredicate predicate : predicates) {
  277. Preconditions.checkArgument(validNames.contains(predicate.getName()), "Field '" + predicate.getName() + "' does not exist in the entity " + clazz.getCanonicalName());
  278. lookup.put(predicate.getName(), predicate);
  279. }
  280. SimpleCompositeBuilder start = new SimpleCompositeBuilder(bufferSize, Equality.GREATER_THAN_EQUALS);
  281. SimpleCompositeBuilder end = new SimpleCompositeBuilder(bufferSize, Equality.LESS_THAN_EQUALS);
  282. // Iterate through components in order while applying predicate to 'start' and 'end'
  283. for (FieldMapper<?> mapper : components) {
  284. for (ColumnPredicate p : lookup.get(mapper.getName())) {
  285. applyPredicate(mapper, start, end, p);
  286. }
  287. }
  288. return new ByteBuffer[]{start.get(), end.get()};
  289. }
  290. private void applyPredicate(FieldMapper<?> mapper, SimpleCompositeBuilder start, SimpleCompositeBuilder end, ColumnPredicate predicate) {
  291. ByteBuffer bb = mapper.valueToByteBuffer(predicate.getValue());
  292. switch (predicate.getOp()) {
  293. case EQUAL:
  294. start.addWithoutControl(bb);
  295. end.addWithoutControl(bb);
  296. break;
  297. case GREATER_THAN:
  298. case GREATER_THAN_EQUALS:
  299. if (mapper.isAscending())
  300. start.add(bb, predicate.getOp());
  301. else
  302. end.add(bb, predicate.getOp());
  303. break;
  304. case LESS_THAN:
  305. case LESS_THAN_EQUALS:
  306. if (mapper.isAscending())
  307. end.add(bb, predicate.getOp());
  308. else
  309. start.add(bb, predicate.getOp());
  310. break;
  311. }
  312. }
  313. }