Java tutorial
/* * Copyright 2013 Gordon Burgett and individual contributors * * 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 org.xflatdb.xflat.db; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.apache.commons.logging.LogFactory; import org.xflatdb.xflat.Cursor; import org.xflatdb.xflat.DuplicateKeyException; import org.xflatdb.xflat.KeyNotFoundException; import org.xflatdb.xflat.Table; import org.xflatdb.xflat.XFlatException; import org.xflatdb.xflat.convert.ConversionException; import org.xflatdb.xflat.convert.ConversionService; import org.xflatdb.xflat.query.XPathQuery; import org.xflatdb.xflat.query.XPathUpdate; import org.jdom2.Element; import org.jdom2.xpath.XPathExpression; import org.xflatdb.xflat.XFlatConstants; /** * A table implementation that converts objects to elements * using the database's conversion service. * @author gordon */ public class ConvertingTable<T> extends TableBase implements Table<T> { private Class<T> tableType; /** * Gets the class of the items in the table. * @return The class object */ protected Class<T> getTableType() { return this.tableType; } private ConversionService conversionService; public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } private final IdAccessor accessor; private final Map<T, String> idMap; private XPathExpression<Object> alternateIdExpression; void setAlternateIdExpression(XPathExpression<Object> expression) { this.alternateIdExpression = expression; } ConvertingTable(Class<T> type, String name) { super(name); this.tableType = type; this.accessor = IdAccessor.forClass(type); if (!this.accessor.hasId()) { //we need to keep a reference to the ID in a weak cache idMap = Collections.synchronizedMap(new WeakHashMap<T, String>()); } else { idMap = null; } } //<editor-fold desc="helpers"> private String getId(Element rowData) { return rowData.getAttributeValue("id", XFlatConstants.xFlatNs); } private String getId(T data) { if (this.accessor.hasId()) { Object id; try { id = this.accessor.getIdValue(data); } catch (IllegalAccessException | InvocationTargetException ex) { throw new XFlatException("Cannot access ID on data", ex); } if (id == null) return null; return this.getIdGenerator().idToString(id); } else if (this.idMap != null) { //hopefully we cached it return this.idMap.get(data); } return null; } private void setId(Element rowData, String sId) { rowData.setAttribute("id", sId, XFlatConstants.xFlatNs); } private Element convert(T data, String id) { Element ret; try { ret = this.conversionService.convert(data, Element.class); } catch (ConversionException ex) { throw new XFlatException("Cannot convert data with ID " + id, ex); } if (id != null) { setId(ret, id); } return ret; } private T convert(Element rowData) { String sId = getId(rowData); T ret; try { ret = this.conversionService.convert(rowData, this.getTableType()); } catch (ConversionException ex) { throw new XFlatException("Cannot convert data with ID " + sId, ex); } if (sId == null) { //can't do any ID stuff, return ret. return ret; } if (!this.accessor.hasId() && this.idMap != null) { //cache the ID this.idMap.put(ret, sId); } else { try { this.accessor.setIdValue(ret, this.getIdGenerator().stringToId(sId, this.accessor.getIdType())); } catch (IllegalAccessException | InvocationTargetException ex) { LogFactory.getLog(getClass()) .warn("Exception setting ID value " + sId + " on type " + this.accessor.getIdType(), ex); } } return ret; } private String getOrGenerateId(T rowData) { if (this.accessor.hasId()) { try { Object id = this.accessor.getIdValue(rowData); if (id != null) { //already have an ID return this.getIdGenerator().idToString(id); } id = this.getIdGenerator().generateNewId(this.accessor.getIdType()); this.accessor.setIdValue(rowData, id); return this.getIdGenerator().idToString(id); } catch (IllegalAccessException | InvocationTargetException ex) { throw new XFlatException("Cannot generate ID", ex); } } else { //if no ID property, always use string if (this.idMap != null) { //doublecheck in the ID map String id; synchronized (this.idMap) { id = this.idMap.get(rowData); if (id == null) { id = (String) this.getIdGenerator().generateNewId(String.class); this.idMap.put(rowData, id); } } return id; } return (String) this.getIdGenerator().generateNewId(String.class); } } //</editor-fold> @Override public void insert(T row) throws DuplicateKeyException { //generate new ID final String id = getOrGenerateId(row); final Element e = convert(row, id); this.doWithEngine(new EngineActionEx<Object, DuplicateKeyException>() { @Override public Object act(Engine engine) throws DuplicateKeyException { engine.insertRow(id, e); return null; } }); } @Override public T find(Object id) { final String sId = this.getIdGenerator().idToString(id); Element data = this.doWithEngine(new EngineAction<Element>() { @Override public Element act(Engine engine) { return engine.readRow(sId); } }); if (data == null) { return null; } T ret = convert(data); return ret; } @Override public T findOne(final XPathQuery query) { Element e = findOneElement(query); if (e == null) { return null; } return convert(e); } private Element findOneElement(XPathQuery query) { try (Cursor<Element> elements = this.queryTable(query)) { Iterator<Element> it = elements.iterator(); if (!it.hasNext()) { return null; } return it.next(); } catch (Exception ex) { throw new XFlatException("Unable to close cursor", ex); } } private Cursor<Element> queryTable(final XPathQuery query) { query.setAlternateIdExpression(alternateIdExpression); return this.doWithEngine(new EngineAction<Cursor<Element>>() { @Override public Cursor<Element> act(Engine engine) { return engine.queryTable(query); } }); } @Override public Cursor<T> find(final XPathQuery query) { query.setAlternateIdExpression(alternateIdExpression); return this.doWithEngine(new EngineAction<Cursor<T>>() { @Override public Cursor<T> act(Engine engine) { return new ConvertingCursor(engine.queryTable(query)); } }); } @Override public List<T> findAll(XPathQuery query) { List<T> ret = new ArrayList<>(); try (Cursor<Element> data = this.queryTable(query)) { for (Element e : data) { ret.add(convert(e)); } } return ret; } @Override public void replace(T newValue) throws KeyNotFoundException { final String id = getId(newValue); if (id == null) { throw new KeyNotFoundException("Object has no ID"); } final Element data = convert(newValue, id); this.doWithEngine(new EngineActionEx<Object, KeyNotFoundException>() { @Override public Object act(Engine engine) throws KeyNotFoundException { engine.replaceRow(id, data); return null; } }); } @Override public boolean replaceOne(XPathQuery query, T newValue) { Element existing = this.findOneElement(query); if (existing == null) { return false; } Element data = convert(newValue, null); String replacedId = recursiveReplaceOne(query, data, existing); if (replacedId == null) { return false; } try { this.accessor.setIdValue(newValue, this.getIdGenerator().stringToId(replacedId, this.accessor.getIdType())); } catch (IllegalAccessException | InvocationTargetException ex) { throw new XFlatException("Unable to update object ID", ex); } return true; } private String recursiveReplaceOne(XPathQuery query, final Element data, Element existing) { if (existing == null) { return null; } final String id = getId(existing); setId(data, id); try { this.doWithEngine(new EngineActionEx<Object, KeyNotFoundException>() { @Override public Object act(Engine engine) throws KeyNotFoundException { engine.replaceRow(id, data); return null; } }); return id; } catch (KeyNotFoundException ex) { //concurrent modification, try again existing = this.findOneElement(query); return recursiveReplaceOne(query, data, existing); } } @Override public boolean upsert(T newValue) { final String id = getOrGenerateId(newValue); final Element data = convert(newValue, id); return this.doWithEngine(new EngineAction<Boolean>() { @Override public Boolean act(Engine engine) { return engine.upsertRow(id, data); } }); } @Override public boolean update(Object id, final XPathUpdate update) throws KeyNotFoundException { if (id == null) { throw new IllegalArgumentException("Id cannot be null"); } final String sId = this.getIdGenerator().idToString(id); return this.doWithEngine(new EngineActionEx<Boolean, KeyNotFoundException>() { @Override public Boolean act(Engine engine) throws KeyNotFoundException { return engine.update(sId, update); } }); } @Override public int update(final XPathQuery query, final XPathUpdate update) { query.setAlternateIdExpression(alternateIdExpression); return this.doWithEngine(new EngineAction<Integer>() { @Override public Integer act(Engine engine) { return engine.update(query, update); } }); } @Override public void delete(Object id) throws KeyNotFoundException { if (id == null) { throw new IllegalArgumentException("id cannot be null"); } final String sId = this.getIdGenerator().idToString(id); this.doWithEngine(new EngineActionEx<Object, KeyNotFoundException>() { @Override public Object act(Engine engine) throws KeyNotFoundException { engine.deleteRow(sId); return null; } }); } @Override public int deleteAll(final XPathQuery query) { query.setAlternateIdExpression(alternateIdExpression); return this.doWithEngine(new EngineAction<Integer>() { @Override public Integer act(Engine engine) { return engine.deleteAll(query); } }); } private class ConvertingCursor implements Cursor<T> { Cursor<Element> rowCursor; public ConvertingCursor(Cursor<Element> rowCursor) { this.rowCursor = rowCursor; } @Override public Iterator<T> iterator() { return new ConvertingCursorIterator(this.rowCursor.iterator()); } @Override public void close() throws XFlatException { this.rowCursor.close(); } } private class ConvertingCursorIterator implements Iterator<T> { Iterator<Element> rowIterator; public ConvertingCursorIterator(Iterator<Element> rowIterator) { this.rowIterator = rowIterator; } @Override public boolean hasNext() { return rowIterator.hasNext(); } @Override public T next() { return convert(rowIterator.next()); } @Override public void remove() { throw new UnsupportedOperationException("Remove not supported on cursors."); } } }