You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by bo...@apache.org on 2015/03/18 00:33:25 UTC
[03/17] incubator-ranger git commit: Support for Solr as Audit
Destination.
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/40aa090d/ranger_solrj/src/main/java/org/apache/solr/common/util/JavaBinCodec.java
----------------------------------------------------------------------
diff --git a/ranger_solrj/src/main/java/org/apache/solr/common/util/JavaBinCodec.java b/ranger_solrj/src/main/java/org/apache/solr/common/util/JavaBinCodec.java
new file mode 100644
index 0000000..687525c
--- /dev/null
+++ b/ranger_solrj/src/main/java/org/apache/solr/common/util/JavaBinCodec.java
@@ -0,0 +1,820 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.solr.common.util;
+
+import org.apache.solr.common.EnumFieldValue;
+import org.noggit.CharArr;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.SolrInputField;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.Map.Entry;
+import java.nio.ByteBuffer;
+
+/**
+ * The class is designed to optimaly serialize/deserialize any supported types in Solr response. As we know there are only a limited type of
+ * items this class can do it with very minimal amount of payload and code. There are 15 known types and if there is an
+ * object in the object tree which does not fall into these types, It must be converted to one of these. Implement an
+ * ObjectResolver and pass it over It is expected that this class is used on both end of the pipes. The class has one
+ * read method and one write method for each of the datatypes
+ * <p>
+ * Note -- Never re-use an instance of this class for more than one marshal or unmarshall operation. Always create a new
+ * instance.
+ */
+public class JavaBinCodec {
+
+ public static final byte
+ NULL = 0,
+ BOOL_TRUE = 1,
+ BOOL_FALSE = 2,
+ BYTE = 3,
+ SHORT = 4,
+ DOUBLE = 5,
+ INT = 6,
+ LONG = 7,
+ FLOAT = 8,
+ DATE = 9,
+ MAP = 10,
+ SOLRDOC = 11,
+ SOLRDOCLST = 12,
+ BYTEARR = 13,
+ ITERATOR = 14,
+ /**
+ * this is a special tag signals an end. No value is associated with it
+ */
+ END = 15,
+
+ SOLRINPUTDOC = 16,
+ SOLRINPUTDOC_CHILDS = 17,
+ ENUM_FIELD_VALUE = 18,
+ MAP_ENTRY = 19,
+ // types that combine tag + length (or other info) in a single byte
+ TAG_AND_LEN = (byte) (1 << 5),
+ STR = (byte) (1 << 5),
+ SINT = (byte) (2 << 5),
+ SLONG = (byte) (3 << 5),
+ ARR = (byte) (4 << 5), //
+ ORDERED_MAP = (byte) (5 << 5), // SimpleOrderedMap (a NamedList subclass, and more common)
+ NAMED_LST = (byte) (6 << 5), // NamedList
+ EXTERN_STRING = (byte) (7 << 5);
+
+
+ private static byte VERSION = 2;
+ private ObjectResolver resolver;
+ protected FastOutputStream daos;
+
+ public JavaBinCodec() {
+ }
+
+ public JavaBinCodec(ObjectResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ public void marshal(Object nl, OutputStream os) throws IOException {
+ init(FastOutputStream.wrap(os));
+ try {
+ daos.writeByte(VERSION);
+ writeVal(nl);
+ } finally {
+ daos.flushBuffer();
+ }
+ }
+
+ /** expert: sets a new output stream */
+ public void init(FastOutputStream os) {
+ daos = os;
+ }
+
+ byte version;
+
+ public Object unmarshal(InputStream is) throws IOException {
+ FastInputStream dis = FastInputStream.wrap(is);
+ version = dis.readByte();
+ if (version != VERSION) {
+ throw new RuntimeException("Invalid version (expected " + VERSION +
+ ", but " + version + ") or the data in not in 'javabin' format");
+ }
+ return readVal(dis);
+ }
+
+
+ public SimpleOrderedMap<Object> readOrderedMap(DataInputInputStream dis) throws IOException {
+ int sz = readSize(dis);
+ SimpleOrderedMap<Object> nl = new SimpleOrderedMap<>();
+ for (int i = 0; i < sz; i++) {
+ String name = (String) readVal(dis);
+ Object val = readVal(dis);
+ nl.add(name, val);
+ }
+ return nl;
+ }
+
+ public NamedList<Object> readNamedList(DataInputInputStream dis) throws IOException {
+ int sz = readSize(dis);
+ NamedList<Object> nl = new NamedList<>();
+ for (int i = 0; i < sz; i++) {
+ String name = (String) readVal(dis);
+ Object val = readVal(dis);
+ nl.add(name, val);
+ }
+ return nl;
+ }
+
+ public void writeNamedList(NamedList<?> nl) throws IOException {
+ writeTag(nl instanceof SimpleOrderedMap ? ORDERED_MAP : NAMED_LST, nl.size());
+ for (int i = 0; i < nl.size(); i++) {
+ String name = nl.getName(i);
+ writeExternString(name);
+ Object val = nl.getVal(i);
+ writeVal(val);
+ }
+ }
+
+ public void writeVal(Object val) throws IOException {
+ if (writeKnownType(val)) {
+ return;
+ } else {
+ Object tmpVal = val;
+ if (resolver != null) {
+ tmpVal = resolver.resolve(val, this);
+ if (tmpVal == null) return; // null means the resolver took care of it fully
+ if (writeKnownType(tmpVal)) return;
+ }
+ }
+
+ writeVal(val.getClass().getName() + ':' + val.toString());
+ }
+
+ protected static final Object END_OBJ = new Object();
+
+ protected byte tagByte;
+
+ public Object readVal(DataInputInputStream dis) throws IOException {
+ tagByte = dis.readByte();
+
+ // if ((tagByte & 0xe0) == 0) {
+ // if top 3 bits are clear, this is a normal tag
+
+ // OK, try type + size in single byte
+ switch (tagByte >>> 5) {
+ case STR >>> 5:
+ return readStr(dis);
+ case SINT >>> 5:
+ return readSmallInt(dis);
+ case SLONG >>> 5:
+ return readSmallLong(dis);
+ case ARR >>> 5:
+ return readArray(dis);
+ case ORDERED_MAP >>> 5:
+ return readOrderedMap(dis);
+ case NAMED_LST >>> 5:
+ return readNamedList(dis);
+ case EXTERN_STRING >>> 5:
+ return readExternString(dis);
+ }
+
+ switch (tagByte) {
+ case NULL:
+ return null;
+ case DATE:
+ return new Date(dis.readLong());
+ case INT:
+ return dis.readInt();
+ case BOOL_TRUE:
+ return Boolean.TRUE;
+ case BOOL_FALSE:
+ return Boolean.FALSE;
+ case FLOAT:
+ return dis.readFloat();
+ case DOUBLE:
+ return dis.readDouble();
+ case LONG:
+ return dis.readLong();
+ case BYTE:
+ return dis.readByte();
+ case SHORT:
+ return dis.readShort();
+ case MAP:
+ return readMap(dis);
+ case SOLRDOC:
+ return readSolrDocument(dis);
+ case SOLRDOCLST:
+ return readSolrDocumentList(dis);
+ case BYTEARR:
+ return readByteArray(dis);
+ case ITERATOR:
+ return readIterator(dis);
+ case END:
+ return END_OBJ;
+ case SOLRINPUTDOC:
+ return readSolrInputDocument(dis);
+ case ENUM_FIELD_VALUE:
+ return readEnumFieldValue(dis);
+ case MAP_ENTRY:
+ return readMapEntry(dis);
+ }
+
+ throw new RuntimeException("Unknown type " + tagByte);
+ }
+
+ public boolean writeKnownType(Object val) throws IOException {
+ if (writePrimitive(val)) return true;
+ if (val instanceof NamedList) {
+ writeNamedList((NamedList<?>) val);
+ return true;
+ }
+ if (val instanceof SolrDocumentList) { // SolrDocumentList is a List, so must come before List check
+ writeSolrDocumentList((SolrDocumentList) val);
+ return true;
+ }
+ if (val instanceof Collection) {
+ writeArray((Collection) val);
+ return true;
+ }
+ if (val instanceof Object[]) {
+ writeArray((Object[]) val);
+ return true;
+ }
+ if (val instanceof SolrDocument) {
+ //this needs special treatment to know which fields are to be written
+ if (resolver == null) {
+ writeSolrDocument((SolrDocument) val);
+ } else {
+ Object retVal = resolver.resolve(val, this);
+ if (retVal != null) {
+ if (retVal instanceof SolrDocument) {
+ writeSolrDocument((SolrDocument) retVal);
+ } else {
+ writeVal(retVal);
+ }
+ }
+ }
+ return true;
+ }
+ if (val instanceof SolrInputDocument) {
+ writeSolrInputDocument((SolrInputDocument)val);
+ return true;
+ }
+ if (val instanceof Map) {
+ writeMap((Map) val);
+ return true;
+ }
+ if (val instanceof Iterator) {
+ writeIterator((Iterator) val);
+ return true;
+ }
+ if (val instanceof Iterable) {
+ writeIterator(((Iterable) val).iterator());
+ return true;
+ }
+ if (val instanceof EnumFieldValue) {
+ writeEnumFieldValue((EnumFieldValue) val);
+ return true;
+ }
+ if (val instanceof Map.Entry) {
+ writeMapEntry((Map.Entry)val);
+ return true;
+ }
+ return false;
+ }
+
+ public void writeTag(byte tag) throws IOException {
+ daos.writeByte(tag);
+ }
+
+ public void writeTag(byte tag, int size) throws IOException {
+ if ((tag & 0xe0) != 0) {
+ if (size < 0x1f) {
+ daos.writeByte(tag | size);
+ } else {
+ daos.writeByte(tag | 0x1f);
+ writeVInt(size - 0x1f, daos);
+ }
+ } else {
+ daos.writeByte(tag);
+ writeVInt(size, daos);
+ }
+ }
+
+ public void writeByteArray(byte[] arr, int offset, int len) throws IOException {
+ writeTag(BYTEARR, len);
+ daos.write(arr, offset, len);
+ }
+
+ public byte[] readByteArray(DataInputInputStream dis) throws IOException {
+ byte[] arr = new byte[readVInt(dis)];
+ dis.readFully(arr);
+ return arr;
+ }
+
+ public void writeSolrDocument(SolrDocument doc) throws IOException {
+ List<SolrDocument> children = doc.getChildDocuments();
+ int sz = doc.size() + (children==null ? 0 : children.size());
+ writeTag(SOLRDOC);
+ writeTag(ORDERED_MAP, sz);
+ for (Map.Entry<String, Object> entry : doc) {
+ String name = entry.getKey();
+ writeExternString(name);
+ Object val = entry.getValue();
+ writeVal(val);
+ }
+ if (children != null) {
+ for (SolrDocument child : children) {
+ writeSolrDocument(child);
+ }
+ }
+ }
+
+ public SolrDocument readSolrDocument(DataInputInputStream dis) throws IOException {
+ tagByte = dis.readByte();
+ int size = readSize(dis);
+ SolrDocument doc = new SolrDocument();
+ for (int i = 0; i < size; i++) {
+ String fieldName;
+ Object obj = readVal(dis); // could be a field name, or a child document
+ if (obj instanceof SolrDocument) {
+ doc.addChildDocument((SolrDocument)obj);
+ continue;
+ } else {
+ fieldName = (String)obj;
+ }
+ Object fieldVal = readVal(dis);
+ doc.setField(fieldName, fieldVal);
+ }
+ return doc;
+ }
+
+ public SolrDocumentList readSolrDocumentList(DataInputInputStream dis) throws IOException {
+ SolrDocumentList solrDocs = new SolrDocumentList();
+ List list = (List) readVal(dis);
+ solrDocs.setNumFound((Long) list.get(0));
+ solrDocs.setStart((Long) list.get(1));
+ solrDocs.setMaxScore((Float) list.get(2));
+
+ @SuppressWarnings("unchecked")
+ List<SolrDocument> l = (List<SolrDocument>) readVal(dis);
+ solrDocs.addAll(l);
+ return solrDocs;
+ }
+
+ public void writeSolrDocumentList(SolrDocumentList docs)
+ throws IOException {
+ writeTag(SOLRDOCLST);
+ List<Number> l = new ArrayList<>(3);
+ l.add(docs.getNumFound());
+ l.add(docs.getStart());
+ l.add(docs.getMaxScore());
+ writeArray(l);
+ writeArray(docs);
+ }
+
+ public SolrInputDocument readSolrInputDocument(DataInputInputStream dis) throws IOException {
+ int sz = readVInt(dis);
+ float docBoost = (Float)readVal(dis);
+ SolrInputDocument sdoc = new SolrInputDocument();
+ sdoc.setDocumentBoost(docBoost);
+ for (int i = 0; i < sz; i++) {
+ float boost = 1.0f;
+ String fieldName;
+ Object obj = readVal(dis); // could be a boost, a field name, or a child document
+ if (obj instanceof Float) {
+ boost = (Float)obj;
+ fieldName = (String)readVal(dis);
+ } else if (obj instanceof SolrInputDocument) {
+ sdoc.addChildDocument((SolrInputDocument)obj);
+ continue;
+ } else {
+ fieldName = (String)obj;
+ }
+ Object fieldVal = readVal(dis);
+ sdoc.setField(fieldName, fieldVal, boost);
+ }
+ return sdoc;
+ }
+
+ public void writeSolrInputDocument(SolrInputDocument sdoc) throws IOException {
+ List<SolrInputDocument> children = sdoc.getChildDocuments();
+ int sz = sdoc.size() + (children==null ? 0 : children.size());
+ writeTag(SOLRINPUTDOC, sz);
+ writeFloat(sdoc.getDocumentBoost());
+ for (SolrInputField inputField : sdoc.values()) {
+ if (inputField.getBoost() != 1.0f) {
+ writeFloat(inputField.getBoost());
+ }
+ writeExternString(inputField.getName());
+ writeVal(inputField.getValue());
+ }
+ if (children != null) {
+ for (SolrInputDocument child : children) {
+ writeSolrInputDocument(child);
+ }
+ }
+ }
+
+
+ public Map<Object,Object> readMap(DataInputInputStream dis)
+ throws IOException {
+ int sz = readVInt(dis);
+ Map<Object,Object> m = new LinkedHashMap<>();
+ for (int i = 0; i < sz; i++) {
+ Object key = readVal(dis);
+ Object val = readVal(dis);
+ m.put(key, val);
+
+ }
+ return m;
+ }
+
+ public void writeIterator(Iterator iter) throws IOException {
+ writeTag(ITERATOR);
+ while (iter.hasNext()) {
+ writeVal(iter.next());
+ }
+ writeVal(END_OBJ);
+ }
+
+ public List<Object> readIterator(DataInputInputStream fis) throws IOException {
+ ArrayList<Object> l = new ArrayList<>();
+ while (true) {
+ Object o = readVal(fis);
+ if (o == END_OBJ) break;
+ l.add(o);
+ }
+ return l;
+ }
+
+ public void writeArray(List l) throws IOException {
+ writeTag(ARR, l.size());
+ for (int i = 0; i < l.size(); i++) {
+ writeVal(l.get(i));
+ }
+ }
+
+ public void writeArray(Collection coll) throws IOException {
+ writeTag(ARR, coll.size());
+ for (Object o : coll) {
+ writeVal(o);
+ }
+
+ }
+
+ public void writeArray(Object[] arr) throws IOException {
+ writeTag(ARR, arr.length);
+ for (int i = 0; i < arr.length; i++) {
+ Object o = arr[i];
+ writeVal(o);
+ }
+ }
+
+ public List<Object> readArray(DataInputInputStream dis) throws IOException {
+ int sz = readSize(dis);
+ ArrayList<Object> l = new ArrayList<>(sz);
+ for (int i = 0; i < sz; i++) {
+ l.add(readVal(dis));
+ }
+ return l;
+ }
+
+ /**
+ * write {@link EnumFieldValue} as tag+int value+string value
+ * @param enumFieldValue to write
+ */
+ public void writeEnumFieldValue(EnumFieldValue enumFieldValue) throws IOException {
+ writeTag(ENUM_FIELD_VALUE);
+ writeInt(enumFieldValue.toInt());
+ writeStr(enumFieldValue.toString());
+ }
+
+ public void writeMapEntry(Entry<Object,Object> val) throws IOException {
+ writeTag(MAP_ENTRY);
+ writeVal(val.getKey());
+ writeVal(val.getValue());
+ }
+
+ /**
+ * read {@link EnumFieldValue} (int+string) from input stream
+ * @param dis data input stream
+ * @return {@link EnumFieldValue}
+ */
+ public EnumFieldValue readEnumFieldValue(DataInputInputStream dis) throws IOException {
+ Integer intValue = (Integer) readVal(dis);
+ String stringValue = (String) readVal(dis);
+ return new EnumFieldValue(intValue, stringValue);
+ }
+
+
+ public Map.Entry<Object,Object> readMapEntry(DataInputInputStream dis) throws IOException {
+ final Object key = readVal(dis);
+ final Object value = readVal(dis);
+ return new Map.Entry<Object,Object>() {
+
+ @Override
+ public Object getKey() {
+ return key;
+ }
+
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "MapEntry[" + key.toString() + ":" + value.toString() + "]";
+ }
+
+ @Override
+ public Object setValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 31;
+ result *=31 + getKey().hashCode();
+ result *=31 + getValue().hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(this == obj) {
+ return true;
+ }
+ if(!(obj instanceof Entry)) {
+ return false;
+ }
+ Map.Entry<Object, Object> entry = (Entry<Object, Object>) obj;
+ return (this.getKey().equals(entry.getKey()) && this.getValue().equals(entry.getValue()));
+ }
+ };
+ }
+
+ /**
+ * write the string as tag+length, with length being the number of UTF-8 bytes
+ */
+ public void writeStr(String s) throws IOException {
+ if (s == null) {
+ writeTag(NULL);
+ return;
+ }
+ int end = s.length();
+ int maxSize = end * 4;
+ if (bytes == null || bytes.length < maxSize) bytes = new byte[maxSize];
+ int sz = ByteUtils.UTF16toUTF8(s, 0, end, bytes, 0);
+
+ writeTag(STR, sz);
+ daos.write(bytes, 0, sz);
+ }
+
+ byte[] bytes;
+ CharArr arr = new CharArr();
+
+ public String readStr(DataInputInputStream dis) throws IOException {
+ int sz = readSize(dis);
+ if (bytes == null || bytes.length < sz) bytes = new byte[sz];
+ dis.readFully(bytes, 0, sz);
+
+ arr.reset();
+ ByteUtils.UTF8toUTF16(bytes, 0, sz, arr);
+ return arr.toString();
+ }
+
+ public void writeInt(int val) throws IOException {
+ if (val > 0) {
+ int b = SINT | (val & 0x0f);
+
+ if (val >= 0x0f) {
+ b |= 0x10;
+ daos.writeByte(b);
+ writeVInt(val >>> 4, daos);
+ } else {
+ daos.writeByte(b);
+ }
+
+ } else {
+ daos.writeByte(INT);
+ daos.writeInt(val);
+ }
+ }
+
+ public int readSmallInt(DataInputInputStream dis) throws IOException {
+ int v = tagByte & 0x0F;
+ if ((tagByte & 0x10) != 0)
+ v = (readVInt(dis) << 4) | v;
+ return v;
+ }
+
+
+ public void writeLong(long val) throws IOException {
+ if ((val & 0xff00000000000000L) == 0) {
+ int b = SLONG | ((int) val & 0x0f);
+ if (val >= 0x0f) {
+ b |= 0x10;
+ daos.writeByte(b);
+ writeVLong(val >>> 4, daos);
+ } else {
+ daos.writeByte(b);
+ }
+ } else {
+ daos.writeByte(LONG);
+ daos.writeLong(val);
+ }
+ }
+
+ public long readSmallLong(DataInputInputStream dis) throws IOException {
+ long v = tagByte & 0x0F;
+ if ((tagByte & 0x10) != 0)
+ v = (readVLong(dis) << 4) | v;
+ return v;
+ }
+
+ public void writeFloat(float val) throws IOException {
+ daos.writeByte(FLOAT);
+ daos.writeFloat(val);
+ }
+
+ public boolean writePrimitive(Object val) throws IOException {
+ if (val == null) {
+ daos.writeByte(NULL);
+ return true;
+ } else if (val instanceof String) {
+ writeStr((String) val);
+ return true;
+ } else if (val instanceof Number) {
+
+ if (val instanceof Integer) {
+ writeInt(((Integer) val).intValue());
+ return true;
+ } else if (val instanceof Long) {
+ writeLong(((Long) val).longValue());
+ return true;
+ } else if (val instanceof Float) {
+ writeFloat(((Float) val).floatValue());
+ return true;
+ } else if (val instanceof Double) {
+ daos.writeByte(DOUBLE);
+ daos.writeDouble(((Double) val).doubleValue());
+ return true;
+ } else if (val instanceof Byte) {
+ daos.writeByte(BYTE);
+ daos.writeByte(((Byte) val).intValue());
+ return true;
+ } else if (val instanceof Short) {
+ daos.writeByte(SHORT);
+ daos.writeShort(((Short) val).intValue());
+ return true;
+ }
+ return false;
+
+ } else if (val instanceof Date) {
+ daos.writeByte(DATE);
+ daos.writeLong(((Date) val).getTime());
+ return true;
+ } else if (val instanceof Boolean) {
+ if ((Boolean) val) daos.writeByte(BOOL_TRUE);
+ else daos.writeByte(BOOL_FALSE);
+ return true;
+ } else if (val instanceof byte[]) {
+ writeByteArray((byte[]) val, 0, ((byte[]) val).length);
+ return true;
+ } else if (val instanceof ByteBuffer) {
+ ByteBuffer buf = (ByteBuffer) val;
+ writeByteArray(buf.array(),buf.position(),buf.limit() - buf.position());
+ return true;
+ } else if (val == END_OBJ) {
+ writeTag(END);
+ return true;
+ }
+ return false;
+ }
+
+
+ public void writeMap(Map<?,?> val) throws IOException {
+ writeTag(MAP, val.size());
+ for (Map.Entry<?,?> entry : val.entrySet()) {
+ Object key = entry.getKey();
+ if (key instanceof String) {
+ writeExternString((String) key);
+ } else {
+ writeVal(key);
+ }
+ writeVal(entry.getValue());
+ }
+ }
+
+
+ public int readSize(DataInputInputStream in) throws IOException {
+ int sz = tagByte & 0x1f;
+ if (sz == 0x1f) sz += readVInt(in);
+ return sz;
+ }
+
+
+ /**
+ * Special method for variable length int (copied from lucene). Usually used for writing the length of a
+ * collection/array/map In most of the cases the length can be represented in one byte (length < 127) so it saves 3
+ * bytes/object
+ *
+ * @throws IOException If there is a low-level I/O error.
+ */
+ public static void writeVInt(int i, FastOutputStream out) throws IOException {
+ while ((i & ~0x7F) != 0) {
+ out.writeByte((byte) ((i & 0x7f) | 0x80));
+ i >>>= 7;
+ }
+ out.writeByte((byte) i);
+ }
+
+ /**
+ * The counterpart for {@link #writeVInt(int, FastOutputStream)}
+ *
+ * @throws IOException If there is a low-level I/O error.
+ */
+ public static int readVInt(DataInputInputStream in) throws IOException {
+ byte b = in.readByte();
+ int i = b & 0x7F;
+ for (int shift = 7; (b & 0x80) != 0; shift += 7) {
+ b = in.readByte();
+ i |= (b & 0x7F) << shift;
+ }
+ return i;
+ }
+
+
+ public static void writeVLong(long i, FastOutputStream out) throws IOException {
+ while ((i & ~0x7F) != 0) {
+ out.writeByte((byte) ((i & 0x7f) | 0x80));
+ i >>>= 7;
+ }
+ out.writeByte((byte) i);
+ }
+
+ public static long readVLong(DataInputInputStream in) throws IOException {
+ byte b = in.readByte();
+ long i = b & 0x7F;
+ for (int shift = 7; (b & 0x80) != 0; shift += 7) {
+ b = in.readByte();
+ i |= (long) (b & 0x7F) << shift;
+ }
+ return i;
+ }
+
+ private int stringsCount = 0;
+ private Map<String, Integer> stringsMap;
+ private List<String> stringsList;
+
+ public void writeExternString(String s) throws IOException {
+ if (s == null) {
+ writeTag(NULL);
+ return;
+ }
+ Integer idx = stringsMap == null ? null : stringsMap.get(s);
+ if (idx == null) idx = 0;
+ writeTag(EXTERN_STRING, idx);
+ if (idx == 0) {
+ writeStr(s);
+ if (stringsMap == null) stringsMap = new HashMap<>();
+ stringsMap.put(s, ++stringsCount);
+ }
+
+ }
+
+ public String readExternString(DataInputInputStream fis) throws IOException {
+ int idx = readSize(fis);
+ if (idx != 0) {// idx != 0 is the index of the extern string
+ return stringsList.get(idx - 1);
+ } else {// idx == 0 means it has a string value
+ String s = (String) readVal(fis);
+ if (stringsList == null) stringsList = new ArrayList<>();
+ stringsList.add(s);
+ return s;
+ }
+ }
+
+
+ public static interface ObjectResolver {
+ public Object resolve(Object o, JavaBinCodec codec) throws IOException;
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/40aa090d/ranger_solrj/src/main/java/org/apache/solr/common/util/JsonRecordReader.java
----------------------------------------------------------------------
diff --git a/ranger_solrj/src/main/java/org/apache/solr/common/util/JsonRecordReader.java b/ranger_solrj/src/main/java/org/apache/solr/common/util/JsonRecordReader.java
new file mode 100644
index 0000000..c3afde7
--- /dev/null
+++ b/ranger_solrj/src/main/java/org/apache/solr/common/util/JsonRecordReader.java
@@ -0,0 +1,586 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.solr.common.util;
+
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.*;
+
+import org.noggit.JSONParser;
+
+import static org.noggit.JSONParser.*;
+
+/**
+ * A Streaming parser for json to emit one record at a time.
+ */
+
+public class JsonRecordReader {
+ public static final String DELIM = ".";
+
+ private Node rootNode = new Node("/", (Node) null);
+
+ public static JsonRecordReader getInst(String split, List<String> fieldMappings) {
+
+ JsonRecordReader jsonRecordReader = new JsonRecordReader(split);
+ for (String s : fieldMappings) {
+ String path = s;
+ int idx = s.indexOf(':');
+ String fieldName = null;
+ if (idx > 0) {
+ fieldName = s.substring(0, idx);
+ path = s.substring(idx + 1);
+ }
+ jsonRecordReader.addField(path, fieldName, true, false);
+ }
+ return jsonRecordReader;
+ }
+
+ /**
+ * A constructor called with a '|' separated list of path expressions
+ * which define sub sections of the JSON stream that are to be emitted as
+ * separate records.
+ *
+ * @param splitPath The PATH for which a record is emitted. Once the
+ * path tag is encountered, the Node.getInst method starts collecting wanted
+ * fields and at the close of the tag, a record is emitted containing all
+ * fields collected since the tag start. Once
+ * emitted the collected fields are cleared. Any fields collected in the
+ * parent tag or above will also be included in the record, but these are
+ * not cleared after emitting the record.
+ * <p>
+ * It uses the ' | ' syntax of PATH to pass in multiple paths.
+ */
+ private JsonRecordReader(String splitPath) {
+ String[] splits = splitPath.split("\\|");
+ for (String split : splits) {
+ split = split.trim();
+ if (split.startsWith("//"))
+ throw new RuntimeException("split cannot start with '//': " + split);
+ if (split.length() == 0)
+ continue;
+ // The created Node has a name set to the full split attribute path
+ addField(split, split, false, true);
+ }
+ }
+
+ /**
+ * Splits the path into a List of segments and calls build() to
+ * construct a tree of Nodes representing path segments. The resulting
+ * tree structure ends up describing all the paths we are interested in.
+ *
+ * @param path The path expression for this field
+ * @param fieldName The name for this field in the emitted record
+ * @param multiValued If 'true' then the emitted record will have values in
+ * a List<String>
+ * @param isRecord Flags that this PATH is from a forEach statement
+ */
+ private void addField(String path, String fieldName, boolean multiValued, boolean isRecord) {
+ if (!path.startsWith("/")) throw new RuntimeException("All paths must start with '/' " + path);
+ List<String> paths = splitEscapeQuote(path);
+ if (paths.size() == 0) {
+ if (isRecord) rootNode.isRecord = true;
+ return;//the patrh is "/"
+ }
+ // deal with how split behaves when seperator starts a string!
+ if ("".equals(paths.get(0).trim()))
+ paths.remove(0);
+ rootNode.build(paths, fieldName, multiValued, isRecord, path);
+ rootNode.buildOptimize();
+ }
+
+ /**
+ * Uses {@link #streamRecords streamRecords} to getInst the JSON source but with
+ * a handler that collects all the emitted records into a single List which
+ * is returned upon completion.
+ *
+ * @param r the stream reader
+ * @return results a List of emitted records
+ */
+ public List<Map<String, Object>> getAllRecords(Reader r) throws IOException {
+ final List<Map<String, Object>> results = new ArrayList<>();
+ streamRecords(r, new Handler() {
+ @Override
+ public void handle(Map<String, Object> record, String path) {
+ results.add(record);
+ }
+ });
+ return results;
+ }
+
+ /**
+ * Creates an JSONParser on top of whatever reader has been
+ * configured. Then calls getInst() with a handler which is
+ * invoked forEach record emitted.
+ *
+ * @param r the stream reader
+ * @param handler The callback instance
+ */
+ public void streamRecords(Reader r, Handler handler) throws IOException {
+ streamRecords(new JSONParser(r), handler);
+ }
+
+ public void streamRecords(JSONParser parser, Handler handler) throws IOException {
+ rootNode.parse(parser, handler,
+ new LinkedHashMap<String, Object>(),
+ new Stack<Set<String>>(), false);
+ }
+
+
+ /**
+ * For each node/leaf in the Node tree there is one object of this class.
+ * This tree of objects represents all the Paths we are interested in.
+ * For each path segment of interest we create a node. In most cases the
+ * node (branch) is rather basic , but for the final portion (leaf) of any
+ * path we add more information to the Node. When parsing the JSON document
+ * we step though this tree as we stream records from the reader. If the JSON
+ * document departs from this tree we skip start tags till we are back on
+ * the tree.
+ */
+ private static class Node {
+ String name; // generally: segment of the path represented by this Node
+ String fieldName; // the fieldname in the emitted record (key of the map)
+ String splitPath; // the full path from the forEach entity attribute
+ final LinkedHashMap<String, Node> childNodes = new LinkedHashMap<>(); // List of immediate child Nodes of this node
+ Node parent; // parent Node in the tree
+ boolean isLeaf = false; // flag: store/emit streamed text for this node
+ boolean isRecord = false; //flag: this Node starts a new record
+ Node wildCardChild;
+ Node recursiveWildCardChild;
+ private boolean useFqn = false;
+
+
+ public Node(String name, Node p) {
+ // Create a basic Node, suitable for the mid portions of any path.
+ // Node.pathName and Node.name are set to same value.
+ this.name = name;
+ parent = p;
+ }
+
+ public Node(String name, String fieldName) {
+ // This is only called from build() when describing an attribute.
+ this.name = name; // a segment from the path
+ this.fieldName = fieldName; // name to store collected values against
+ }
+
+
+ /**
+ * Walk the Node tree propagating any wildDescentant information to
+ * child nodes.
+ */
+ private void buildOptimize() {
+ if (parent != null && parent.recursiveWildCardChild != null && this.recursiveWildCardChild == null) {
+ this.recursiveWildCardChild = parent.recursiveWildCardChild;
+ }
+ for (Node n : childNodes.values()) n.buildOptimize();
+ }
+ static final String WILDCARD_PATH = "*";
+ static final String RECURSIVE_WILDCARD_PATH = "**";
+
+ /**
+ * Build a Node tree structure representing all paths of intrest to us.
+ * This must be done before parsing of the JSON stream starts. Each node
+ * holds one portion of an path. Taking each path segment in turn this
+ * method walks the Node tree and finds where the new segment should be
+ * inserted. It creates a Node representing a field's name, PATH and
+ * some flags and inserts the Node into the Node tree.
+ */
+ private void build(
+ List<String> paths, // a List of segments from the split paths
+ String fieldName, // the fieldName assoc with this path
+ boolean multiValued, // flag if this fieldName is multiValued or not
+ boolean record, // is this path a record or a field
+ String path) {
+ // recursively walk the paths Lists adding new Nodes as required
+ String segment = paths.remove(0); // shift out next path segment
+
+ if (segment.length() < 1) throw new RuntimeException("all pieces in path must be non empty " + path);
+
+ // does this "name" already exist as a child node.
+ Node n = getOrAddNode(segment, childNodes);
+ if (paths.isEmpty()) {
+ // We have emptied paths, we are for the moment a leaf of the tree.
+ // When parsing the actual input we have traversed to a position
+ // where we actutally have to do something. getOrAddNode() will
+ // have created and returned a new minimal Node with name and
+ // pathName already populated. We need to add more information.
+ if (record) {
+ //wild cards cannot be used in split
+ assert !WILDCARD_PATH.equals(n.name);
+ assert !RECURSIVE_WILDCARD_PATH.equals(n.name);
+ // split attribute
+ n.isRecord = true; // flag: split attribute, prepare to emit rec
+ n.splitPath = fieldName; // the full split attribute path
+ } else {
+ if (n.name.equals(WILDCARD_PATH)) {
+ wildCardChild = n;
+ }
+ if (n.name.equals(RECURSIVE_WILDCARD_PATH)) {
+ recursiveWildCardChild = n.recursiveWildCardChild = n;
+ }
+
+ // path with content we want to store and return
+ n.isLeaf = true; // we have to store text found here
+ n.fieldName = fieldName; // name to store collected text against
+ if ("$FQN".equals(n.fieldName)) {
+ n.fieldName = null;
+ n.useFqn = true;
+ }
+ }
+ } else {
+ //wildcards must only come at the end
+ if (WILDCARD_PATH.equals(name) || RECURSIVE_WILDCARD_PATH.equals(name))
+ throw new RuntimeException("wild cards are allowed only in the end " + path);
+ // recurse to handle next paths segment
+ n.build(paths, fieldName, multiValued, record, path);
+ }
+ }
+
+ private Node getOrAddNode(String pathName, Map<String, Node> children) {
+ Node n = children.get(pathName);
+ if (n != null) return n;
+ // new territory! add a new node for this path bitty
+ children.put(pathName, n = new Node(pathName, this));
+ return n;
+ }
+
+ /**
+ * Copies a supplied Map to a new Map which is returned. Used to copy a
+ * records values. If a fields value is a List then they have to be
+ * deep-copied for thread safety
+ */
+ private static Map<String, Object> getDeepCopy(Map<String, Object> values) {
+ Map<String, Object> result = new LinkedHashMap<>();
+ for (Map.Entry<String, Object> entry : values.entrySet()) {
+ if (entry.getValue() instanceof List) {
+ result.put(entry.getKey(), new ArrayList((List) entry.getValue()));
+ } else {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return result;
+ }
+
+ private void parse(JSONParser parser,
+ Handler handler,
+ Map<String, Object> values,
+ Stack<Set<String>> stack, // lists of values to purge
+ boolean recordStarted) throws IOException {
+
+ int event = -1;
+ for (; ; ) {
+ event = parser.nextEvent();
+ if (event == EOF) break;
+ if (event == OBJECT_START) {
+ handleObjectStart(parser, new HashSet<Node>(), handler, values, stack, recordStarted, null);
+ } else if (event == ARRAY_START) {
+ for (; ; ) {
+ event = parser.nextEvent();
+ if (event == ARRAY_END) break;
+ if (event == OBJECT_START) {
+ handleObjectStart(parser, new HashSet<Node>(), handler, values, stack, recordStarted, null);
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * If a new tag is encountered, check if it is of interest or not by seeing
+ * if it matches against our node tree. If we have deperted from the node
+ * tree then walk back though the tree's ancestor nodes checking to see if
+ * any // expressions exist for the node and compare them against the new
+ * tag. If matched then "jump" to that node, otherwise ignore the tag.
+ * <p>
+ * Note, the list of // expressions found while walking back up the tree
+ * is chached in the HashMap decends. Then if the new tag is to be skipped,
+ * any inner chil tags are compared against the cache and jumped to if
+ * matched.
+ */
+ private void handleObjectStart(final JSONParser parser, final Set<Node> childrenFound,
+ final Handler handler, final Map<String, Object> values,
+ final Stack<Set<String>> stack, boolean recordStarted,
+ MethodFrameWrapper frameWrapper)
+ throws IOException {
+
+ final boolean isRecordStarted = recordStarted || isRecord;
+ Set<String> valuesAddedinThisFrame = null;
+ if (isRecord) {
+ // This Node is a match for an PATH from a forEach attribute,
+ // prepare for the clean up that will occurr when the record
+ // is emitted after its END_ELEMENT is matched
+ valuesAddedinThisFrame = new HashSet<>();
+ stack.push(valuesAddedinThisFrame);
+ } else if (recordStarted) {
+ // This node is a child of some parent which matched against forEach
+ // attribute. Continue to add values to an existing record.
+ valuesAddedinThisFrame = stack.peek();
+ }
+
+ class Wrapper extends MethodFrameWrapper {
+ Wrapper(Node node, MethodFrameWrapper parent, String name) {
+ this.node = node;
+ this.parent = parent;
+ this.name = name;
+ }
+
+ @Override
+ public void walk(int event) throws IOException {
+ if (event == OBJECT_START) {
+ node.handleObjectStart(parser, childrenFound, handler, values, stack, isRecordStarted, this);
+ } else if (event == ARRAY_START) {
+ for (; ; ) {
+ event = parser.nextEvent();
+ if (event == ARRAY_END) break;
+ if (event == OBJECT_START) {
+ node.handleObjectStart(parser, childrenFound, handler, values, stack, isRecordStarted, this);
+ }
+ }
+ }
+
+ }
+ }
+
+ try {
+ for (; ; ) {
+ int event = parser.nextEvent();
+ if (event == OBJECT_END) {
+ if (isRecord()) {
+ handler.handle(getDeepCopy(values), splitPath);
+ }
+ return;
+ }
+ assert event == STRING;
+ assert parser.wasKey();
+ String name = parser.getString();
+
+ Node node = childNodes.get(name);
+ if (node == null) node = wildCardChild;
+ if (node == null) node = recursiveWildCardChild;
+
+ if (node != null) {
+ if (node.isLeaf) {//this is a leaf collect data here
+ event = parser.nextEvent();
+ String nameInRecord = node.fieldName == null ? getNameInRecord(name, frameWrapper, node) : node.fieldName;
+ MethodFrameWrapper runnable = null;
+ if (event == OBJECT_START || event == ARRAY_START) {
+ if (node.recursiveWildCardChild != null) runnable = new Wrapper(node, frameWrapper, name);
+ }
+ Object val = parseSingleFieldValue(event, parser, runnable);
+ if (val != null) {
+ putValue(values, nameInRecord, val);
+ if (isRecordStarted) valuesAddedinThisFrame.add(nameInRecord);
+ }
+
+ } else {
+ event = parser.nextEvent();
+ new Wrapper(node, frameWrapper, name).walk(event);
+ }
+ } else {
+ //this is not something we are interested in . skip it
+ event = parser.nextEvent();
+ if (event == STRING ||
+ event == LONG ||
+ event == NUMBER ||
+ event == BIGNUMBER ||
+ event == BOOLEAN ||
+ event == NULL) {
+ continue;
+ }
+ if (event == ARRAY_START) {
+ consumeTillMatchingEnd(parser, 0, 1);
+ continue;
+ }
+ if (event == OBJECT_START) {
+ consumeTillMatchingEnd(parser, 1, 0);
+ continue;
+ } else throw new RuntimeException("unexpected token " + event);
+
+ }
+ }
+ } finally {
+ if ((isRecord() || !isRecordStarted) && !stack.empty()) {
+ Set<String> cleanThis = stack.pop();
+ if (cleanThis != null) {
+ for (String fld : cleanThis) {
+ values.remove(fld);
+ }
+ }
+ }
+ }
+ }
+
+ private String getNameInRecord(String name, MethodFrameWrapper frameWrapper, Node n) {
+ if (frameWrapper == null || !n.useFqn) return name;
+ StringBuilder sb = new StringBuilder();
+ frameWrapper.prependName(sb);
+ return sb.append(DELIM).append(name).toString();
+ }
+
+ private boolean isRecord() {
+ return isRecord;
+ }
+
+
+ private void putValue(Map<String, Object> values, String fieldName, Object o) {
+ if (o == null) return;
+ Object val = values.get(fieldName);
+ if (val == null) {
+ values.put(fieldName, o);
+ return;
+ }
+ if (val instanceof List) {
+ List list = (List) val;
+ list.add(o);
+ return;
+ }
+ ArrayList l = new ArrayList();
+ l.add(val);
+ l.add(o);
+ values.put(fieldName, l);
+ }
+
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ } // end of class Node
+
+
+ /**
+ * The path is split into segments using the '/' as a seperator. However
+ * this method deals with special cases where there is a slash '/' character
+ * inside the attribute value e.g. x/@html='text/html'. We split by '/' but
+ * then reassemble things were the '/' appears within a quoted sub-string.
+ * <p>
+ * We have already enforced that the string must begin with a seperator. This
+ * method depends heavily on how split behaves if the string starts with the
+ * seperator or if a sequence of multiple seperator's appear.
+ */
+ private static List<String> splitEscapeQuote(String str) {
+ List<String> result = new LinkedList<>();
+ String[] ss = str.split("/");
+ for (int i = 0; i < ss.length; i++) { // i=1: skip seperator at start of string
+ StringBuilder sb = new StringBuilder();
+ int quoteCount = 0;
+ while (true) {
+ sb.append(ss[i]);
+ for (int j = 0; j < ss[i].length(); j++)
+ if (ss[i].charAt(j) == '\'') quoteCount++;
+ // have we got a split inside quoted sub-string?
+ if ((quoteCount % 2) == 0) break;
+ // yes!; replace the '/' and loop to concat next token
+ i++;
+ sb.append("/");
+ }
+ result.add(sb.toString());
+ }
+ return result;
+ }
+
+
+ /**
+ * Implement this interface to stream records as and when one is found.
+ */
+ public static interface Handler {
+ /**
+ * @param record The record map. The key is the field name as provided in
+ * the addField() methods. The value can be a single String (for single
+ * valued fields) or a List<String> (for multiValued).
+ * @param path The forEach path for which this record is being emitted
+ * If there is any change all parsing will be aborted and the Exception
+ * is propagated up
+ */
+ public void handle(Map<String, Object> record, String path);
+ }
+
+ public static Object parseSingleFieldValue(int ev, JSONParser parser, MethodFrameWrapper runnable) throws IOException {
+ switch (ev) {
+ case STRING:
+ return parser.getString();
+ case LONG:
+ return parser.getLong();
+ case NUMBER:
+ return parser.getDouble();
+ case BIGNUMBER:
+ return parser.getNumberChars().toString();
+ case BOOLEAN:
+ return parser.getBoolean();
+ case NULL:
+ parser.getNull();
+ return null;
+ case ARRAY_START:
+ return parseArrayFieldValue(ev, parser, runnable);
+ case OBJECT_START:
+ if (runnable != null) {
+ runnable.walk(OBJECT_START);
+ return null;
+ }
+ consumeTillMatchingEnd(parser, 1, 0);
+ return null;
+ default:
+ throw new RuntimeException("Error parsing JSON field value. Unexpected " + JSONParser.getEventString(ev));
+ }
+ }
+
+ static abstract class MethodFrameWrapper {
+ Node node;
+ MethodFrameWrapper parent;
+ String name;
+
+ void prependName(StringBuilder sb) {
+ if (parent != null) {
+ parent.prependName(sb);
+ sb.append(DELIM);
+ }
+ sb.append(name);
+ }
+
+ public abstract void walk(int event) throws IOException;
+ }
+
+ public static List<Object> parseArrayFieldValue(int ev, JSONParser parser, MethodFrameWrapper runnable) throws IOException {
+ assert ev == ARRAY_START;
+
+ ArrayList lst = new ArrayList(2);
+ for (; ; ) {
+ ev = parser.nextEvent();
+ if (ev == ARRAY_END) {
+ if (lst.isEmpty()) return null;
+ return lst;
+ }
+ Object val = parseSingleFieldValue(ev, parser, runnable);
+ if (val != null) lst.add(val);
+ }
+ }
+
+ public static void consumeTillMatchingEnd(JSONParser parser, int obj, int arr) throws IOException {
+ for (; ; ) {
+ int event = parser.nextEvent();
+ if (event == OBJECT_START) obj++;
+ if (event == OBJECT_END) obj--;
+ assert obj >= 0;
+ if (event == ARRAY_START) arr++;
+ if (event == ARRAY_END) arr--;
+ assert arr >= 0;
+ if (obj == 0 && arr == 0) break;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/40aa090d/ranger_solrj/src/main/java/org/apache/solr/common/util/NamedList.java
----------------------------------------------------------------------
diff --git a/ranger_solrj/src/main/java/org/apache/solr/common/util/NamedList.java b/ranger_solrj/src/main/java/org/apache/solr/common/util/NamedList.java
new file mode 100644
index 0000000..c539b2b
--- /dev/null
+++ b/ranger_solrj/src/main/java/org/apache/solr/common/util/NamedList.java
@@ -0,0 +1,708 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.solr.common.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.common.SolrException;
+
+/**
+ * A simple container class for modeling an ordered list of name/value pairs.
+ *
+ * <p>
+ * Unlike Maps:
+ * </p>
+ * <ul>
+ * <li>Names may be repeated</li>
+ * <li>Order of elements is maintained</li>
+ * <li>Elements may be accessed by numeric index</li>
+ * <li>Names and Values can both be null</li>
+ * </ul>
+ *
+ * <p>
+ * A NamedList provides fast access by element number, but not by name.
+ * </p>
+ * <p>
+ * When a NamedList is serialized, order is considered more important than access
+ * by key, so ResponseWriters that output to a format such as JSON will normally
+ * choose a data structure that allows order to be easily preserved in various
+ * clients (i.e. not a straight map).
+ * If access by key is more important for serialization, see {@link SimpleOrderedMap},
+ * or simply use a regular {@link Map}
+ * </p>
+ *
+ */
+public class NamedList<T> implements Cloneable, Serializable, Iterable<Map.Entry<String,T>> {
+
+ private static final long serialVersionUID = 1957981902839867821L;
+ protected final List<Object> nvPairs;
+
+ /** Creates an empty instance */
+ public NamedList() {
+ nvPairs = new ArrayList<>();
+ }
+
+ /**
+ * Creates a NamedList instance containing the "name,value" pairs contained in the
+ * Entry[].
+ *
+ * <p>
+ * Modifying the contents of the Entry[] after calling this constructor may change
+ * the NamedList (in future versions of Solr), but this is not guaranteed and should
+ * not be relied upon. To modify the NamedList, refer to {@link #add(String, Object)}
+ * or {@link #remove(String)}.
+ * </p>
+ *
+ * @param nameValuePairs the name value pairs
+ */
+ public NamedList(Map.Entry<String, ? extends T>[] nameValuePairs) {
+ nvPairs = nameValueMapToList(nameValuePairs);
+ }
+
+ /**
+ * Creates a NamedList instance containing the "name,value" pairs contained in the
+ * Map.
+ *
+ * <p>
+ * Modifying the contents of the Map after calling this constructor may change
+ * the NamedList (in future versions of Solr), but this is not guaranteed and should
+ * not be relied upon. To modify the NamedList, refer to {@link #add(String, Object)}
+ * or {@link #remove(String)}.
+ * </p>
+ *
+ * @param nameValueMap the name value pairs
+ */
+ public NamedList(Map<String,? extends T> nameValueMap) {
+ if (null == nameValueMap) {
+ nvPairs = new ArrayList<>();
+ } else {
+ nvPairs = new ArrayList<>(nameValueMap.size());
+ for (Map.Entry<String,? extends T> ent : nameValueMap.entrySet()) {
+ nvPairs.add(ent.getKey());
+ nvPairs.add(ent.getValue());
+ }
+ }
+ }
+
+ /**
+ * Creates an instance backed by an explicitly specified list of
+ * pairwise names/values.
+ *
+ * <p>
+ * When using this constructor, runtime type safety is only guaranteed if
+ * all even numbered elements of the input list are of type "T".
+ * </p>
+ *
+ * @param nameValuePairs underlying List which should be used to implement a NamedList
+ * @deprecated Use {@link #NamedList(java.util.Map.Entry[])} for the NamedList instantiation
+ */
+ @Deprecated
+ public NamedList(List<Object> nameValuePairs) {
+ nvPairs=nameValuePairs;
+ }
+
+ /**
+ * Method to serialize Map.Entry<String, ?> to a List in which the even
+ * indexed elements (0,2,4. ..etc) are Strings and odd elements (1,3,5,) are of
+ * the type "T".
+ *
+ * @return Modified List as per the above description
+ * @deprecated This a temporary placeholder method until the guts of the class
+ * are actually replaced by List<String, ?>.
+ * @see <a href="https://issues.apache.org/jira/browse/SOLR-912">SOLR-912</a>
+ */
+ @Deprecated
+ private List<Object> nameValueMapToList(Map.Entry<String, ? extends T>[] nameValuePairs) {
+ List<Object> result = new ArrayList<>();
+ for (Map.Entry<String, ?> ent : nameValuePairs) {
+ result.add(ent.getKey());
+ result.add(ent.getValue());
+ }
+ return result;
+ }
+
+ /** The total number of name/value pairs */
+ public int size() {
+ return nvPairs.size() >> 1;
+ }
+
+ /**
+ * The name of the pair at the specified List index
+ *
+ * @return null if no name exists
+ */
+ public String getName(int idx) {
+ return (String)nvPairs.get(idx << 1);
+ }
+
+ /**
+ * The value of the pair at the specified List index
+ *
+ * @return may be null
+ */
+ @SuppressWarnings("unchecked")
+ public T getVal(int idx) {
+ return (T)nvPairs.get((idx << 1) + 1);
+ }
+
+ /**
+ * Adds a name/value pair to the end of the list.
+ */
+ public void add(String name, T val) {
+ nvPairs.add(name);
+ nvPairs.add(val);
+ }
+
+ /**
+ * Modifies the name of the pair at the specified index.
+ */
+ public void setName(int idx, String name) {
+ nvPairs.set(idx<<1, name);
+ }
+
+ /**
+ * Modifies the value of the pair at the specified index.
+ *
+ * @return the value that used to be at index
+ */
+ public T setVal(int idx, T val) {
+ int index = (idx<<1)+1;
+ @SuppressWarnings("unchecked")
+ T old = (T)nvPairs.get( index );
+ nvPairs.set(index, val);
+ return old;
+ }
+
+ /**
+ * Removes the name/value pair at the specified index.
+ *
+ * @return the value at the index removed
+ */
+ public T remove(int idx) {
+ int index = (idx<<1);
+ nvPairs.remove(index);
+ @SuppressWarnings("unchecked")
+ T result = (T)nvPairs.remove(index); // same index, as things shifted in previous remove
+ return result;
+ }
+
+ /**
+ * Scans the list sequentially beginning at the specified index and
+ * returns the index of the first pair with the specified name.
+ *
+ * @param name name to look for, may be null
+ * @param start index to begin searching from
+ * @return The index of the first matching pair, -1 if no match
+ */
+ public int indexOf(String name, int start) {
+ int sz = size();
+ for (int i=start; i<sz; i++) {
+ String n = getName(i);
+ if (name==null) {
+ if (n==null) return i; // matched null
+ } else if (name.equals(n)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets the value for the first instance of the specified name
+ * found.
+ * <p>
+ * NOTE: this runs in linear time (it scans starting at the
+ * beginning of the list until it finds the first pair with
+ * the specified name).
+ *
+ * @return null if not found or if the value stored was null.
+ * @see #indexOf
+ * @see #get(String,int)
+ *
+ */
+ public T get(String name) {
+ return get(name,0);
+ }
+
+ /**
+ * Gets the value for the first instance of the specified name
+ * found starting at the specified index.
+ * <p>
+ * NOTE: this runs in linear time (it scans starting at the
+ * specified position until it finds the first pair with
+ * the specified name).
+ *
+ * @return null if not found or if the value stored was null.
+ * @see #indexOf
+ */
+ public T get(String name, int start) {
+ int sz = size();
+ for (int i=start; i<sz; i++) {
+ String n = getName(i);
+ if (name==null) {
+ if (n==null) return getVal(i);
+ } else if (name.equals(n)) {
+ return getVal(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the values for the the specified name
+ *
+ * @param name Name
+ * @return List of values
+ */
+ public List<T> getAll(String name) {
+ List<T> result = new ArrayList<>();
+ int sz = size();
+ for (int i = 0; i < sz; i++) {
+ String n = getName(i);
+ if (name==n || (name!=null && name.equals(n))) {
+ result.add(getVal(i));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Removes all values matching the specified name
+ *
+ * @param name Name
+ */
+ private void killAll(String name) {
+ int sz = size();
+ // Go through the list backwards, removing matches as found.
+ for (int i = sz - 1; i >= 0; i--) {
+ String n = getName(i);
+ if (name==n || (name!=null && name.equals(n))) {
+ remove(i);
+ }
+ }
+ }
+
+ /**
+ * Recursively parses the NamedList structure to arrive at a specific element.
+ * As you descend the NamedList tree, the last element can be any type,
+ * including NamedList, but the previous elements MUST be NamedList objects
+ * themselves. A null value is returned if the indicated hierarchy doesn't
+ * exist, but NamedList allows null values so that could be the actual value
+ * at the end of the path.
+ *
+ * This method is particularly useful for parsing the response from Solr's
+ * /admin/mbeans handler, but it also works for any complex structure.
+ *
+ * Explicitly casting the return value is recommended. An even safer option is
+ * to accept the return value as an object and then check its type.
+ *
+ * Usage examples:
+ *
+ * String coreName = (String) response.findRecursive
+ * ("solr-mbeans", "CORE", "core", "stats", "coreName");
+ * long numDoc = (long) response.findRecursive
+ * ("solr-mbeans", "CORE", "searcher", "stats", "numDocs");
+ *
+ * @param args
+ * One or more strings specifying the tree to navigate.
+ * @return the last entry in the given path hierarchy, null if not found.
+ */
+ public Object findRecursive(String... args) {
+ NamedList<?> currentList = null;
+ Object value = null;
+ for (int i = 0; i < args.length; i++) {
+ String key = args[i];
+ /*
+ * The first time through the loop, the current list is null, so we assign
+ * it to this list. Then we retrieve the first key from this list and
+ * assign it to value.
+ *
+ * On the next loop, we check whether the retrieved value is a NamedList.
+ * If it is, then we drop down to that NamedList, grab the value of the
+ * next key, and start the loop over. If it is not a NamedList, then we
+ * assign the value to null and break out of the loop.
+ *
+ * Assigning the value to null and then breaking out of the loop seems
+ * like the wrong thing to do, but there's a very simple reason that it
+ * works: If we have reached the last key, then the loop ends naturally
+ * after we retrieve the value, and that code is never executed.
+ */
+ if (currentList == null) {
+ currentList = this;
+ } else {
+ if (value instanceof NamedList) {
+ currentList = (NamedList<?>) value;
+ } else {
+ value = null;
+ break;
+ }
+ }
+ /*
+ * We do not need to do a null check on currentList for the following
+ * assignment. The instanceof check above will fail if the current list is
+ * null, and if that happens, the loop will end before this point.
+ */
+ value = currentList.get(key, 0);
+ }
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ int sz = size();
+ for (int i=0; i<sz; i++) {
+ if (i != 0) sb.append(',');
+ sb.append(getName(i));
+ sb.append('=');
+ sb.append(getVal(i));
+ }
+ sb.append('}');
+
+ return sb.toString();
+ }
+
+ public NamedList getImmutableCopy() {
+ NamedList copy = clone();
+ return new NamedList<>( Collections.unmodifiableList(copy.nvPairs));
+ }
+
+ public Map asMap(int maxDepth) {
+ LinkedHashMap result = new LinkedHashMap();
+ for(int i=0;i<size();i++){
+ Object val = getVal(i);
+ if (val instanceof NamedList && maxDepth> 0) {
+ //the maxDepth check is to avoid stack overflow due to infinite recursion
+ val = ((NamedList) val).asMap(maxDepth-1);
+ }
+ Object old = result.put(getName(i), val);
+ if(old!=null){
+ if (old instanceof List) {
+ List list = (List) old;
+ list.add(val);
+ result.put(getName(i),old);
+ } else {
+ ArrayList l = new ArrayList();
+ l.add(old);
+ l.add(val);
+ result.put(getName(i), l);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ *
+ * Helper class implementing Map.Entry<String, T> to store the key-value
+ * relationship in NamedList (the keys of which are String-s)
+ */
+ public static final class NamedListEntry<T> implements Map.Entry<String,T> {
+
+ public NamedListEntry() {
+
+ }
+
+ public NamedListEntry(String _key, T _value) {
+ key = _key;
+ value = _value;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public T getValue() {
+ return value;
+ }
+
+ @Override
+ public T setValue(T _value) {
+ T oldValue = value;
+ value = _value;
+ return oldValue;
+ }
+
+ private String key;
+
+ private T value;
+ }
+
+ /**
+ * Iterates over the Map and sequentially adds its key/value pairs
+ */
+ public boolean addAll(Map<String,T> args) {
+ for (Map.Entry<String, T> entry : args.entrySet() ) {
+ add(entry.getKey(), entry.getValue());
+ }
+ return args.size()>0;
+ }
+
+ /** Appends the elements of the given NamedList to this one. */
+ public boolean addAll(NamedList<T> nl) {
+ nvPairs.addAll(nl.nvPairs);
+ return nl.size()>0;
+ }
+
+ /**
+ * Makes a <i>shallow copy</i> of the named list.
+ */
+ @Override
+ public NamedList<T> clone() {
+ ArrayList<Object> newList = new ArrayList<>(nvPairs.size());
+ newList.addAll(nvPairs);
+ return new NamedList<>(newList);
+ }
+
+ //----------------------------------------------------------------------------
+ // Iterable interface
+ //----------------------------------------------------------------------------
+
+ /**
+ * Support the Iterable interface
+ */
+ @Override
+ public Iterator<Map.Entry<String,T>> iterator() {
+
+ final NamedList<T> list = this;
+
+ Iterator<Map.Entry<String,T>> iter = new Iterator<Map.Entry<String,T>>() {
+
+ int idx = 0;
+
+ @Override
+ public boolean hasNext() {
+ return idx < list.size();
+ }
+
+ @Override
+ public Map.Entry<String,T> next() {
+ final int index = idx++;
+ Map.Entry<String,T> nv = new Map.Entry<String,T>() {
+ @Override
+ public String getKey() {
+ return list.getName( index );
+ }
+
+ @Override
+ public T getValue() {
+ return list.getVal( index );
+ }
+
+ @Override
+ public String toString() {
+ return getKey()+"="+getValue();
+ }
+
+ @Override
+ public T setValue(T value) {
+ return list.setVal(index, value);
+ }
+ };
+ return nv;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ return iter;
+ }
+
+ /**
+ * NOTE: this runs in linear time (it scans starting at the
+ * beginning of the list until it finds the first pair with
+ * the specified name).
+ */
+ public T remove(String name) {
+ int idx = indexOf(name, 0);
+ if(idx != -1) return remove(idx);
+ return null;
+ }
+
+ /**
+ * Removes and returns all values for the specified name. Returns null if
+ * no matches found. This method will return all matching objects,
+ * regardless of data type. If you are parsing Solr config options, the
+ * {@link #removeConfigArgs(String)} or {@link #removeBooleanArg(String)}
+ * methods will probably work better.
+ *
+ * @param name Name
+ * @return List of values
+ */
+ public List<T> removeAll(String name) {
+ List<T> result = new ArrayList<>();
+ result = getAll(name);
+ if (result.size() > 0 ) {
+ killAll(name);
+ return result;
+ }
+ return null;
+ }
+
+ /**
+ * Used for getting a boolean argument from a NamedList object. If the name
+ * is not present, returns null. If there is more than one value with that
+ * name, or if the value found is not a Boolean or a String, throws an
+ * exception. If there is only one value present and it is a Boolean or a
+ * String, the value is removed and returned as a Boolean. If an exception
+ * is thrown, the NamedList is not modified. See {@link #removeAll(String)}
+ * and {@link #removeConfigArgs(String)} for additional ways of gathering
+ * configuration information from a NamedList.
+ *
+ * @param name
+ * The key to look up in the NamedList.
+ * @return The boolean value found.
+ * @throws SolrException
+ * If multiple values are found for the name or the value found is
+ * not a Boolean or a String.
+ */
+ public Boolean removeBooleanArg(final String name) {
+ Boolean bool = getBooleanArg(name);
+ if (null != bool) {
+ remove(name);
+ }
+ return bool;
+ }
+
+ /**
+ * Used for getting a boolean argument from a NamedList object. If the name
+ * is not present, returns null. If there is more than one value with that
+ * name, or if the value found is not a Boolean or a String, throws an
+ * exception. If there is only one value present and it is a Boolean or a
+ * String, the value is returned as a Boolean. The NamedList is not
+ * modified. See {@link #remove(String)}, {@link #removeAll(String)}
+ * and {@link #removeConfigArgs(String)} for additional ways of gathering
+ * configuration information from a NamedList.
+ *
+ * @param name The key to look up in the NamedList.
+ * @return The boolean value found.
+ * @throws SolrException
+ * If multiple values are found for the name or the value found is
+ * not a Boolean or a String.
+ */
+ public Boolean getBooleanArg(final String name) {
+ Boolean bool;
+ List<T> values = getAll(name);
+ if (0 == values.size()) {
+ return null;
+ }
+ if (values.size() > 1) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+ "Only one '" + name + "' is allowed");
+ }
+ Object o = get(name);
+ if (o instanceof Boolean) {
+ bool = (Boolean)o;
+ } else if (o instanceof CharSequence) {
+ bool = Boolean.parseBoolean(o.toString());
+ } else {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+ "'" + name + "' must have type Boolean or CharSequence; found " + o.getClass());
+ }
+ return bool;
+ }
+
+ /**
+ * Used for getting one or many arguments from NamedList objects that hold
+ * configuration parameters. Finds all entries in the NamedList that match
+ * the given name. If they are all strings or arrays of strings, remove them
+ * from the NamedList and return the individual elements as a {@link Collection}.
+ * Parameter order will be preserved if the returned collection is handled as
+ * an {@link ArrayList}. Throws SolrException if any of the values associated
+ * with the name are not strings or arrays of strings. If exception is
+ * thrown, the NamedList is not modified. Returns an empty collection if no
+ * matches found. If you need to remove and retrieve all matching items from
+ * the NamedList regardless of data type, use {@link #removeAll(String)} instead.
+ * The {@link #removeBooleanArg(String)} method can be used for retrieving a
+ * boolean argument.
+ *
+ * @param name
+ * The key to look up in the NamedList.
+ * @return A collection of the values found.
+ * @throws SolrException
+ * If values are found for the input key that are not strings or
+ * arrays of strings.
+ */
+ @SuppressWarnings("rawtypes")
+ public Collection<String> removeConfigArgs(final String name)
+ throws SolrException {
+ List<T> objects = getAll(name);
+ List<String> collection = new ArrayList<>(size() / 2);
+ final String err = "init arg '" + name + "' must be a string "
+ + "(ie: 'str'), or an array (ie: 'arr') containing strings; found: ";
+
+ for (Object o : objects) {
+ if (o instanceof String) {
+ collection.add((String) o);
+ continue;
+ }
+
+ // If it's an array, convert to List (which is a Collection).
+ if (o instanceof Object[]) {
+ o = Arrays.asList((Object[]) o);
+ }
+
+ // If it's a Collection, collect each value.
+ if (o instanceof Collection) {
+ for (Object item : (Collection) o) {
+ if (!(item instanceof String)) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, err + item.getClass());
+ }
+ collection.add((String) item);
+ }
+ continue;
+ }
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, err + o.getClass());
+ }
+
+ if (collection.size() > 0) {
+ killAll(name);
+ }
+
+ return collection;
+ }
+
+ public void clear() {
+ nvPairs.clear();
+ }
+
+ @Override
+ public int hashCode() {
+ return nvPairs.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof NamedList)) return false;
+ NamedList<?> nl = (NamedList<?>) obj;
+ return this.nvPairs.equals(nl.nvPairs);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/40aa090d/ranger_solrj/src/main/java/org/apache/solr/common/util/ObjectReleaseTracker.java
----------------------------------------------------------------------
diff --git a/ranger_solrj/src/main/java/org/apache/solr/common/util/ObjectReleaseTracker.java b/ranger_solrj/src/main/java/org/apache/solr/common/util/ObjectReleaseTracker.java
new file mode 100644
index 0000000..47ab21a
--- /dev/null
+++ b/ranger_solrj/src/main/java/org/apache/solr/common/util/ObjectReleaseTracker.java
@@ -0,0 +1,62 @@
+package org.apache.solr.common.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ObjectReleaseTracker {
+ public static Map<Object,String> OBJECTS = new ConcurrentHashMap<>();
+
+ public static boolean track(Object object) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ new ObjectTrackerException().printStackTrace(pw);
+ OBJECTS.put(object, sw.toString());
+ return true;
+ }
+
+ public static boolean release(Object object) {
+ OBJECTS.remove(object);
+ return true;
+ }
+
+ public static boolean clearObjectTrackerAndCheckEmpty() {
+ Set<Entry<Object,String>> entries = OBJECTS.entrySet();
+ boolean empty = entries.isEmpty();
+ if (entries.size() > 0) {
+ System.err.println("ObjectTracker found objects that were not released!!!");
+ }
+
+ for (Entry<Object,String> entry : entries) {
+ System.err.println(entry.getValue());
+ }
+
+ OBJECTS.clear();
+
+ return empty;
+ }
+
+ private static class ObjectTrackerException extends RuntimeException {
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/40aa090d/ranger_solrj/src/main/java/org/apache/solr/common/util/RetryUtil.java
----------------------------------------------------------------------
diff --git a/ranger_solrj/src/main/java/org/apache/solr/common/util/RetryUtil.java b/ranger_solrj/src/main/java/org/apache/solr/common/util/RetryUtil.java
new file mode 100644
index 0000000..83ee100
--- /dev/null
+++ b/ranger_solrj/src/main/java/org/apache/solr/common/util/RetryUtil.java
@@ -0,0 +1,43 @@
+package org.apache.solr.common.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import java.util.concurrent.TimeUnit;
+
+public class RetryUtil {
+ public static interface RetryCmd {
+ public void execute() throws Throwable;
+ }
+
+ public static void retryOnThrowable(Class clazz, long timeoutms, long intervalms, RetryCmd cmd) throws Throwable {
+ long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeoutms, TimeUnit.MILLISECONDS);
+ while (true) {
+ try {
+ cmd.execute();
+ } catch (Throwable t) {
+ if (clazz.isInstance(t) && System.nanoTime() < timeout) {
+ Thread.sleep(intervalms);
+ continue;
+ }
+ throw t;
+ }
+ // success
+ break;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/40aa090d/ranger_solrj/src/main/java/org/apache/solr/common/util/SimpleOrderedMap.java
----------------------------------------------------------------------
diff --git a/ranger_solrj/src/main/java/org/apache/solr/common/util/SimpleOrderedMap.java b/ranger_solrj/src/main/java/org/apache/solr/common/util/SimpleOrderedMap.java
new file mode 100644
index 0000000..c9996d1
--- /dev/null
+++ b/ranger_solrj/src/main/java/org/apache/solr/common/util/SimpleOrderedMap.java
@@ -0,0 +1,67 @@
+package org.apache.solr.common.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import java.util.*;
+
+
+/** <code>SimpleOrderedMap</code> is a {@link NamedList} where access by key is more
+ * important than maintaining order when it comes to representing the
+ * held data in other forms, as ResponseWriters normally do.
+ * It's normally not a good idea to repeat keys or use null keys, but this
+ * is not enforced. If key uniqueness enforcement is desired, use a regular {@link Map}.
+ * <p>
+ * For example, a JSON response writer may choose to write a SimpleOrderedMap
+ * as {"foo":10,"bar":20} and may choose to write a NamedList as
+ * ["foo",10,"bar",20]. An XML response writer may choose to render both
+ * the same way.
+ * </p>
+ * <p>
+ * This class does not provide efficient lookup by key, its main purpose is
+ * to hold data to be serialized. It aims to minimize overhead and to be
+ * efficient at adding new elements.
+ * </p>
+ */
+public class SimpleOrderedMap<T> extends NamedList<T> {
+ /** Creates an empty instance */
+ public SimpleOrderedMap() {
+ super();
+ }
+
+ /**
+ * Creates an instance backed by an explicitly specified list of
+ * pairwise names/values.
+ *
+ * @param nameValuePairs underlying List which should be used to implement a SimpleOrderedMap; modifying this List will affect the SimpleOrderedMap.
+ */
+ @Deprecated
+ public SimpleOrderedMap(List<Object> nameValuePairs) {
+ super(nameValuePairs);
+ }
+
+ public SimpleOrderedMap(Map.Entry<String, T>[] nameValuePairs) {
+ super(nameValuePairs);
+ }
+
+ @Override
+ public SimpleOrderedMap<T> clone() {
+ ArrayList<Object> newList = new ArrayList<>(nvPairs.size());
+ newList.addAll(nvPairs);
+ return new SimpleOrderedMap<>(newList);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/40aa090d/ranger_solrj/src/main/java/org/apache/solr/common/util/SolrjNamedThreadFactory.java
----------------------------------------------------------------------
diff --git a/ranger_solrj/src/main/java/org/apache/solr/common/util/SolrjNamedThreadFactory.java b/ranger_solrj/src/main/java/org/apache/solr/common/util/SolrjNamedThreadFactory.java
new file mode 100644
index 0000000..2a7c901
--- /dev/null
+++ b/ranger_solrj/src/main/java/org/apache/solr/common/util/SolrjNamedThreadFactory.java
@@ -0,0 +1,50 @@
+package org.apache.solr.common.util;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+public class SolrjNamedThreadFactory implements ThreadFactory {
+ private static final AtomicInteger poolNumber = new AtomicInteger(1);
+ private final ThreadGroup group;
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+ private final String prefix;
+
+ public SolrjNamedThreadFactory(String namePrefix) {
+ SecurityManager s = System.getSecurityManager();
+ group = (s != null)? s.getThreadGroup() :
+ Thread.currentThread().getThreadGroup();
+ prefix = namePrefix + "-" +
+ poolNumber.getAndIncrement() +
+ "-thread-";
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(group, r,
+ prefix + threadNumber.getAndIncrement(),
+ 0);
+
+ t.setDaemon(false);
+
+ if (t.getPriority() != Thread.NORM_PRIORITY)
+ t.setPriority(Thread.NORM_PRIORITY);
+ return t;
+ }
+}
\ No newline at end of file