/* * 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.util.ArrayList; import java.util.Iterator; import java.util.List; import org.jdom2.Element; import org.xflatdb.xflat.Cursor; import org.xflatdb.xflat.DuplicateKeyException; import org.xflatdb.xflat.KeyNotFoundException; import org.xflatdb.xflat.KeyValueTable; import org.xflatdb.xflat.XFlatException; import org.xflatdb.xflat.convert.ConversionException; import org.xflatdb.xflat.convert.ConversionNotSupportedException; import org.xflatdb.xflat.convert.ConversionService; import org.xflatdb.xflat.query.XPathQuery; import org.xflatdb.xflat.query.XPathUpdate; import org.xflatdb.xflat.util.Action1; /** * A KeyValueTable implementation using the XFlat conversion service * to convert values to XML. * @author Gordon */ public class ConvertingKeyValueTable extends TableBase implements KeyValueTable { private ConversionService conversionService; public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } private Action1 loadPojoMapperAction = new Action1<ConvertingKeyValueTable>(){ @Override public void apply(ConvertingKeyValueTable val) { } }; void setLoadPojoMapperAction(Action1 action) { this.loadPojoMapperAction = action; } public ConvertingKeyValueTable(String tableName){ super(tableName); } private <T> Element convert(T data, String key){ Element ret; try { try{ ret = this.conversionService.convert(data, Element.class); } catch(ConversionNotSupportedException ex){ //load pojo mapper and try one more time... this.loadPojoMapperAction.apply(this); ret = this.conversionService.convert(data, Element.class); } } catch (ConversionException ex) { throw new XFlatException("Cannot convert data with key " + key, ex); } return ret; } private <T> T convert(Element rowData, String key, Class<T> clazz){ T ret; try { try{ ret = this.conversionService.convert(rowData, clazz); } catch(ConversionNotSupportedException ex){ //load pojo mapper and try one more time... this.loadPojoMapperAction.apply(this); ret = this.conversionService.convert(rowData, clazz); } } catch (ConversionException ex) { throw new XFlatException("Cannot convert data with key " + key, ex); } return ret; } @Override public <T> void add(final String key, T row) throws DuplicateKeyException { final Element e = convert(row, key); this.doWithEngine(new EngineActionEx<Object, DuplicateKeyException>(){ @Override public Object act(Engine engine) throws DuplicateKeyException { engine.insertRow(key, e); return null; } }); } @Override public <T> void set(final String key, T row){ final Element data = convert(row, key); this.doWithEngine(new EngineAction(){ @Override public Object act(Engine engine) { engine.upsertRow(key, data); return null; } }); } @Override public <T> T put(final String key, T row) { Class<? extends T> clazz = (Class<? extends T>)row.getClass(); final Element data = convert(row, key); Element inRow; inRow = this.doWithEngine(new EngineAction<Element>(){ @Override public Element act(Engine engine) { while(true){ Element inRow = engine.readRow(key); if(inRow == null){ try { engine.insertRow(key, data); return inRow; } catch (DuplicateKeyException ex) { //try again by re-reading the row } } else{ try { return engine.replaceRow(key, data); } catch (KeyNotFoundException ex) { //try again from reading the row } } } } }); if(inRow == null) return null; return convert(inRow, key, clazz); } @Override public <T> T get(final String key, Class<T> clazz) { Element data = this.doWithEngine(new EngineAction<Element>(){ @Override public Element act(Engine engine) { return engine.readRow(key); } }); if(data == null){ return null; } T ret = convert(data, key, clazz); return ret; } @Override public <T> T findOne(XPathQuery query, Class<T> clazz) { Element e = findOneElement(query); if(e == null){ return null; } return convert(e, "unknown", clazz); } 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){ return this.doWithEngine(new EngineAction<Cursor<Element>>(){ @Override public Cursor<Element> act(Engine engine) { return engine.queryTable(query); } }); } @Override public <T> Cursor<T> find(final XPathQuery query, final Class<T> clazz) { return this.doWithEngine(new EngineAction<Cursor<T>>(){ @Override public Cursor<T> act(Engine engine) { return new ConvertingCursor(engine.queryTable(query), clazz); } }); } @Override public <T> List<T> findAll(XPathQuery query, Class<T> clazz) { List<T> ret = new ArrayList<>(); try(Cursor<Element> data = this.queryTable(query)){ for(Element e : data){ ret.add(convert(e, "unknown", clazz)); } } return ret; } @Override public <T> void replace(final String key, T newValue) throws KeyNotFoundException { final Element data = convert(newValue, key); this.doWithEngine(new EngineActionEx<Object, KeyNotFoundException>(){ @Override public Object act(Engine engine) throws KeyNotFoundException { engine.replaceRow(key, data); return null; } }); } @Override public boolean update(final String key, final XPathUpdate update) throws KeyNotFoundException { return this.doWithEngine(new EngineActionEx<Boolean, KeyNotFoundException>(){ @Override public Boolean act(Engine engine) throws KeyNotFoundException { return engine.update(key, update); } }); } @Override public void delete(final String key) throws KeyNotFoundException { this.doWithEngine(new EngineActionEx<Object, KeyNotFoundException>(){ @Override public Object act(Engine engine) throws KeyNotFoundException { engine.deleteRow(key); return null; } }); } @Override public int deleteAll(final XPathQuery query) { return this.doWithEngine(new EngineAction<Integer>(){ @Override public Integer act(Engine engine) { return engine.deleteAll(query); } }); } private class ConvertingCursor<T> implements Cursor<T>{ Cursor<Element> rowCursor; Class<T> clazz; public ConvertingCursor(Cursor<Element> rowCursor, Class<T> clazz){ this.rowCursor = rowCursor; this.clazz = clazz; } @Override public Iterator<T> iterator() { return new ConvertingCursorIterator(this.rowCursor.iterator(), clazz); } @Override public void close() throws XFlatException { this.rowCursor.close(); } } private class ConvertingCursorIterator<T> implements Iterator<T>{ Iterator<Element> rowIterator; Class<T> clazz; public ConvertingCursorIterator(Iterator<Element> rowIterator, Class<T> clazz){ this.rowIterator = rowIterator; this.clazz = clazz; } @Override public boolean hasNext() { return rowIterator.hasNext(); } @Override public T next() { return convert(rowIterator.next(), "unknown", clazz); } @Override public void remove() { throw new UnsupportedOperationException("Remove not supported on cursors."); } } }