/* Copyright (c) 2012-2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.storage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; import java.util.List; import javax.annotation.Nullable; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.RevCommit; import org.locationtech.geogig.api.RevFeature; import org.locationtech.geogig.api.RevFeatureType; import org.locationtech.geogig.api.RevObject; import org.locationtech.geogig.api.RevObject.TYPE; import org.locationtech.geogig.api.RevTag; import org.locationtech.geogig.api.RevTree; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.io.Closeables; import com.ning.compress.lzf.LZFInputStream; import com.ning.compress.lzf.LZFOutputStream; /** * Provides a base implementation for different representations of the {@link ObjectDatabase}. * * @see ObjectDatabase */ public abstract class AbstractObjectDatabase implements ObjectDatabase { protected ObjectSerializingFactory serializationFactory; public AbstractObjectDatabase(final ObjectSerializingFactory serializationFactory) { Preconditions.checkNotNull(serializationFactory); this.serializationFactory = serializationFactory; } /** * Searches the database for {@link ObjectId}s that match the given partial id. * * @param partialId the partial id to search for * @return a list of matching results * @see org.locationtech.geogig.storage.ObjectDatabase#lookUp(java.lang.String) */ @Override public List<ObjectId> lookUp(final String partialId) { Preconditions.checkNotNull(partialId); byte[] raw = ObjectId.toRaw(partialId); List<ObjectId> baseResults = lookUpInternal(raw); // If the length of the partial string is odd, then the last character wasn't considered in // the lookup, we need to filter the list further. if (partialId.length() % 2 != 0) { Iterator<ObjectId> listIterator = baseResults.iterator(); while (listIterator.hasNext()) { ObjectId result = listIterator.next(); if (!result.toString().startsWith(partialId)) { listIterator.remove(); } } } return baseResults; } /** * Searches the database for {@link ObjectId}s that match the given raw byte code. * * @param raw raw byte code to search for * @return a list of matching results */ protected abstract List<ObjectId> lookUpInternal(byte[] raw); @Override public RevObject get(ObjectId id) { Preconditions.checkNotNull(id, "id"); final ObjectReader<RevObject> reader = serializationFactory.createObjectReader(); return get(id, reader, true); } @Override public @Nullable RevObject getIfPresent(ObjectId id) { Preconditions.checkNotNull(id, "id"); final ObjectReader<RevObject> reader = serializationFactory.createObjectReader(); return get(id, reader, false); } /** * Reads an object with the given {@link ObjectId id} out of the database. * * @param id the id of the object to read * @param reader the reader of the object * @return the object, as read in from the {@link ObjectReader} * @see org.locationtech.geogig.storage.ObjectDatabase#get(org.locationtech.geogig.api.ObjectId, * org.locationtech.geogig.storage.ObjectReader) */ @Override public <T extends RevObject> T get(final ObjectId id, final Class<T> clazz) { Preconditions.checkNotNull(id, "id"); Preconditions.checkNotNull(clazz, "class"); final ObjectReader<T> reader = serializationFactory.createObjectReader(getType(clazz)); return get(id, reader, true); } @Override public @Nullable <T extends RevObject> T getIfPresent(ObjectId id, Class<T> clazz) throws IllegalArgumentException { Preconditions.checkNotNull(id, "id"); Preconditions.checkNotNull(clazz, "class"); final ObjectReader<T> reader = serializationFactory.createObjectReader(getType(clazz)); return get(id, reader, false); } private <T extends RevObject> T get(final ObjectId id, final ObjectReader<T> reader, boolean failIfNotFound) { InputStream raw = getRaw(id, failIfNotFound); if (null == raw) { return null; } T object; try { object = reader.read(id, raw); } finally { Closeables.closeQuietly(raw); } Preconditions.checkState(id.equals(object.getId()), "Expected id doesn't match parsed id %s, %s. Object: %s", id, object.getId(), object); return object; } @Override public RevTree getTree(ObjectId id) { return get(id, RevTree.class); } @Override public RevFeature getFeature(ObjectId id) { return get(id, RevFeature.class); } @Override public RevFeatureType getFeatureType(ObjectId id) { return get(id, RevFeatureType.class); } @Override public RevCommit getCommit(ObjectId id) { return get(id, RevCommit.class); } @Override public RevTag getTag(ObjectId id) { return get(id, RevTag.class); } private RevObject.TYPE getType(Class<? extends RevObject> clazz) { return TYPE.valueOf(clazz); } @Nullable private InputStream getRaw(final ObjectId id, boolean failIfNotFound) throws IllegalArgumentException { InputStream in = getRawInternal(id, failIfNotFound); if (null == in) { return null; } try { return new LZFInputStream(in); } catch (IOException e) { throw Throwables.propagate(e); } } protected abstract InputStream getRawInternal(ObjectId id, boolean failIfNotFound) throws IllegalArgumentException; @Override public boolean put(final RevObject object) { Preconditions.checkNotNull(object); Preconditions.checkArgument(!object.getId().isNull(), "ObjectId is NULL %s", object); ByteArrayOutputStream rawOut = new ByteArrayOutputStream(); writeObject(object, rawOut); final ObjectId id = object.getId(); final byte[] rawData = rawOut.toByteArray(); final boolean inserted = putInternal(id, rawData); return inserted; } /** * This default implementation calls {@link #putInternal(ObjectId, byte[])} for each object; * subclasses may override if appropriate. */ @Override public void putAll(Iterator<? extends RevObject> objects, final BulkOpListener listener) { ByteArrayOutputStream rawOut = new ByteArrayOutputStream(); while (objects.hasNext()) { RevObject object = objects.next(); rawOut.reset(); writeObject(object, rawOut); final byte[] rawData = rawOut.toByteArray(); final ObjectId id = object.getId(); final boolean added = putInternal(id, rawData); if (added) { listener.inserted(object.getId(), rawData.length); } else { listener.found(object.getId(), null); } } } protected void writeObject(RevObject object, OutputStream target) { ObjectWriter<RevObject> writer = serializationFactory.createObjectWriter(object.getType()); LZFOutputStream cOut = new LZFOutputStream(target); try { writer.write(object, cOut); } catch (IOException e) { throw Throwables.propagate(e); } finally { try { cOut.flush(); cOut.close(); } catch (Exception e) { throw Throwables.propagate(e); } } // int size = ((ByteArrayOutputStream) target).size(); // System.err.printf("%d,%s,%s\n", size, object.getId(), object.getType()); } /** * Stores the raw data for the given id <em>only if it does not exist</em> already, and returns * whether the object was actually inserted. */ protected abstract boolean putInternal(ObjectId id, byte[] rawData); /** * @return a newly constructed {@link ObjectInserter} for this database * @see org.locationtech.geogig.storage.ObjectDatabase#newObjectInserter() */ @Override public ObjectInserter newObjectInserter() { return new ObjectInserter(this); } @Override public Iterator<RevObject> getAll(final Iterable<ObjectId> ids) { return getAll(ids, BulkOpListener.NOOP_LISTENER); } @Override public void putAll(Iterator<? extends RevObject> objects) { putAll(objects, BulkOpListener.NOOP_LISTENER); } @Override public long deleteAll(Iterator<ObjectId> ids) { return deleteAll(ids, BulkOpListener.NOOP_LISTENER); } }