Java tutorial
/******************************************************************************* * Copyright 2011 Netflix * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.netflix.astyanax.thrift; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Map.Entry; import org.apache.cassandra.thrift.CfDef; import org.apache.cassandra.thrift.ColumnDef; import org.apache.cassandra.thrift.SliceRange; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.thrift.TEnum; import org.apache.thrift.TFieldIdEnum; import org.apache.thrift.meta_data.FieldMetaData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.netflix.astyanax.Serializer; public class ThriftUtils { private final static Logger LOG = LoggerFactory.getLogger(ThriftUtils.class); public static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(new byte[0]); // private static final SliceRange RANGE_ALL = new SliceRange(EMPTY_BYTE_BUFFER, EMPTY_BYTE_BUFFER, false, Integer.MAX_VALUE); public static final int MUTATION_OVERHEAD = 20; public static SliceRange createAllInclusiveSliceRange() { return new SliceRange(EMPTY_BYTE_BUFFER, EMPTY_BYTE_BUFFER, false, Integer.MAX_VALUE); } public static <C> SliceRange createSliceRange(Serializer<C> serializer, C startColumn, C endColumn, boolean reversed, int limit) { return new SliceRange((startColumn == null) ? EMPTY_BYTE_BUFFER : serializer.toByteBuffer(startColumn), (endColumn == null) ? EMPTY_BYTE_BUFFER : serializer.toByteBuffer(endColumn), reversed, limit); } public static <T extends org.apache.thrift.TBase> Properties getPropertiesFromThrift(T entity) throws Exception { Properties props = new Properties(); setPropertiesFromThrift("", props, entity); return props; } /** * Quick and dirty implementation that converts thrift DDL to a Properties object by flattening * the parameters * @param prefix * @param properties * @param entity * @throws Exception */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static void setPropertiesFromThrift(String prefix, Properties properties, org.apache.thrift.TBase entity) throws Exception { Field field = entity.getClass().getDeclaredField("metaDataMap"); Map<org.apache.thrift.TFieldIdEnum, org.apache.thrift.meta_data.FieldMetaData> fields = (Map<org.apache.thrift.TFieldIdEnum, FieldMetaData>) field .get(entity); for (Entry<org.apache.thrift.TFieldIdEnum, FieldMetaData> f : fields.entrySet()) { ThriftTypes type = ThriftTypes.values()[f.getValue().valueMetaData.type]; Object value = entity.getFieldValue(f.getKey()); if (value == null) continue; switch (type) { case VOID: break; case BOOL: case BYTE: case DOUBLE: case I16: case I32: case I64: case STRING: case ENUM: if (value instanceof byte[]) { properties.put(prefix + f.getKey().getFieldName(), Base64.encodeBase64String((byte[]) value)); } else if (value instanceof ByteBuffer) { properties.put(prefix + f.getKey().getFieldName(), base64Encode((ByteBuffer) value)); } else { properties.put(prefix + f.getKey().getFieldName(), value.toString()); } break; case MAP: { String newPrefix = prefix + f.getKey().getFieldName() + "."; org.apache.thrift.meta_data.MapMetaData meta = (org.apache.thrift.meta_data.MapMetaData) f .getValue().valueMetaData; if (!meta.keyMetaData.isStruct() && !meta.keyMetaData.isContainer()) { Map<Object, Object> map = (Map<Object, Object>) value; for (Entry<Object, Object> entry : map.entrySet()) { properties.put(newPrefix + entry.getKey(), entry.getValue().toString()); } } else { LOG.error(String.format("Unable to serializer field '%s' key type '%s' not supported", f.getKey().getFieldName(), meta.keyMetaData.getTypedefName())); } break; } case LIST: { String newPrefix = prefix + f.getKey().getFieldName() + "."; List<Object> list = (List<Object>) value; org.apache.thrift.meta_data.ListMetaData listMeta = (org.apache.thrift.meta_data.ListMetaData) f .getValue().valueMetaData; for (Object entry : list) { String id; if (entry instanceof CfDef) { id = ((CfDef) entry).name; } else if (entry instanceof ColumnDef) { ByteBuffer name = ((ColumnDef) entry).name; id = base64Encode(name); } else { LOG.error("Don't know how to convert to properties " + listMeta.elemMetaData.getTypedefName()); continue; } if (listMeta.elemMetaData.isStruct()) { setPropertiesFromThrift(newPrefix + id + ".", properties, (org.apache.thrift.TBase) entry); } else { properties.put(newPrefix + id, entry); } } break; } case STRUCT: { setPropertiesFromThrift(prefix + f.getKey().getFieldName() + ".", properties, (org.apache.thrift.TBase) value); break; } case SET: default: LOG.error("Unhandled value : " + f.getKey().getFieldName() + " " + type); break; } } } private static String base64Encode(ByteBuffer bb) { if (bb == null) { return ""; } byte[] nbb = new byte[bb.remaining()]; bb.duplicate().get(nbb, 0, bb.remaining()); return Base64.encodeBase64String(nbb); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static <T> T getThriftObjectFromProperties(Class<T> clazz, Properties props) throws Exception { org.apache.thrift.TBase entity = (org.apache.thrift.TBase) clazz.newInstance(); return (T) populateObjectFromProperties(entity, props); } public static Object populateObjectFromProperties(Object entity, Properties props) throws Exception { return populateObject(entity, propertiesToMap(props)); } @SuppressWarnings({ "rawtypes", "unchecked" }) private static Object populateObject(Object obj, Map<String, Object> map) throws Exception { org.apache.thrift.TBase entity = (org.apache.thrift.TBase) obj; Field field = entity.getClass().getDeclaredField("metaDataMap"); Map<org.apache.thrift.TFieldIdEnum, org.apache.thrift.meta_data.FieldMetaData> fields = (Map<org.apache.thrift.TFieldIdEnum, FieldMetaData>) field .get(entity); for (Entry<TFieldIdEnum, FieldMetaData> f : fields.entrySet()) { Object value = map.get(f.getKey().getFieldName()); if (value != null) { ThriftTypes type = ThriftTypes.values()[f.getValue().valueMetaData.type]; switch (type) { case VOID: break; case BYTE: case BOOL: case DOUBLE: case I16: case I32: case I64: case STRING: try { entity.setFieldValue(f.getKey(), valueForBasicType(value, f.getValue().valueMetaData.type)); } catch (ClassCastException e) { if (e.getMessage().contains(ByteBuffer.class.getCanonicalName())) { entity.setFieldValue(f.getKey(), ByteBuffer.wrap(Base64.decodeBase64((String) value))); } else { throw e; } } break; case ENUM: { org.apache.thrift.meta_data.EnumMetaData meta = (org.apache.thrift.meta_data.EnumMetaData) f .getValue().valueMetaData; Object e = meta.enumClass; entity.setFieldValue(f.getKey(), Enum.valueOf((Class<Enum>) e, (String) value)); break; } case MAP: { org.apache.thrift.meta_data.MapMetaData meta = (org.apache.thrift.meta_data.MapMetaData) f .getValue().valueMetaData; if (!meta.keyMetaData.isStruct() && !meta.keyMetaData.isContainer()) { Map<Object, Object> childMap = (Map<Object, Object>) value; Map<Object, Object> childEntityMap = Maps.newHashMap(); entity.setFieldValue(f.getKey(), childEntityMap); if (!meta.keyMetaData.isStruct() && !meta.keyMetaData.isContainer()) { for (Entry<Object, Object> entry : childMap.entrySet()) { Object childKey = valueForBasicType(entry.getKey(), meta.keyMetaData.type); Object childValue = valueForBasicType(entry.getValue(), meta.valueMetaData.type); childEntityMap.put(childKey, childValue); } } } else { LOG.error(String.format("Unable to serializer field '%s' key type '%s' not supported", f.getKey().getFieldName(), meta.keyMetaData.getTypedefName())); } break; } case LIST: { Map<String, Object> childMap = (Map<String, Object>) value; org.apache.thrift.meta_data.ListMetaData listMeta = (org.apache.thrift.meta_data.ListMetaData) f .getValue().valueMetaData; // Create an empty list and attach to the parent entity List<Object> childList = Lists.newArrayList(); entity.setFieldValue(f.getKey(), childList); if (listMeta.elemMetaData instanceof org.apache.thrift.meta_data.StructMetaData) { org.apache.thrift.meta_data.StructMetaData structMeta = (org.apache.thrift.meta_data.StructMetaData) listMeta.elemMetaData; for (Entry<String, Object> childElement : childMap.entrySet()) { org.apache.thrift.TBase childEntity = structMeta.structClass.newInstance(); populateObject(childEntity, (Map<String, Object>) childElement.getValue()); childList.add(childEntity); } } break; } case STRUCT: { break; } case SET: default: LOG.error("Unhandled value : " + f.getKey().getFieldName() + " " + type); break; } } } return entity; } public static Object valueForBasicType(Object value, byte type) { switch (ThriftTypes.values()[type]) { case BYTE: return Byte.parseByte((String) value); case BOOL: return Boolean.parseBoolean((String) value); case DOUBLE: return Double.parseDouble((String) value); case I16: return Short.parseShort((String) value); case I32: return Integer.parseInt((String) value); case I64: return Long.parseLong((String) value); case STRING: return value; default: return null; } } /** * Convert a Properties object into a tree * @param props * @return */ public static Map<String, Object> propertiesToMap(Properties props) { Map<String, Object> root = Maps.newTreeMap(); for (Entry<Object, Object> prop : props.entrySet()) { String[] parts = StringUtils.split((String) prop.getKey(), "."); Map<String, Object> node = root; for (int i = 0; i < parts.length - 1; i++) { if (!node.containsKey(parts[i])) { node.put(parts[i], new LinkedHashMap<String, Object>()); } node = (Map<String, Object>) node.get(parts[i]); } node.put(parts[parts.length - 1], (String) prop.getValue()); } return root; } }