/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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.ext.uberfire.social.activities.persistence; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import com.google.gson.Gson; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.StringUtils; import org.ext.uberfire.social.activities.model.SocialActivitiesEvent; import org.uberfire.io.IOService; import org.uberfire.java.nio.channels.SeekableByteChannel; import org.uberfire.java.nio.file.Path; import org.uberfire.java.nio.file.StandardOpenOption; public class SocialFile { private final Path path; private final Gson gson; private final String JSON_SEPARATOR = "01"; private final IOService ioService; private byte[] JSON_SEPARATOR_BYTES; private SeekableByteChannel reader; private long currentCursorReadPosition; private ByteBuffer byteBufferSize; public SocialFile(final Path path, final IOService ioService, final Gson gson) { try { JSON_SEPARATOR_BYTES = Hex.decodeHex(JSON_SEPARATOR.toCharArray()); } catch (DecoderException e) { throw new ErrorReadingFile(); } this.byteBufferSize = ByteBuffer.allocate("a".getBytes().length); this.path = path; this.gson = gson; this.currentCursorReadPosition = -1; this.ioService = ioService; } public void write(List<SocialActivitiesEvent> events) throws IOException { try { ioService.startBatch(path.getFileSystem()); SeekableByteChannel sbc = ioService.newByteChannel(path); for (SocialActivitiesEvent event : events) { String json = gson.toJson(event); writeJson(json, sbc); writeSeparator(sbc); writeJsonSize(sbc, json); writeSeparator(sbc); } sbc.close(); } finally { ioService.endBatch(); } } private void writeJsonSize(SeekableByteChannel sbc, String json) throws IOException { String jsonLenght = String.valueOf(json.getBytes().length); ByteBuffer bfSrc = ByteBuffer.wrap(jsonLenght.getBytes()); sbc.write(bfSrc); } private void writeSeparator(SeekableByteChannel sbc) throws IOException { ByteBuffer bfSrc = ByteBuffer.wrap(JSON_SEPARATOR_BYTES); sbc.write(bfSrc); } private void writeJson(String json, SeekableByteChannel sbc) throws IOException { ByteBuffer bfSrc = ByteBuffer.wrap(json.getBytes()); sbc.write(bfSrc); } public void prepareForReading() throws IOException { reader = ioService.newByteChannel(path, StandardOpenOption.READ); currentCursorReadPosition = reader.size() - 1; } private void reverseSearchForSeparatorPosition() throws IOException { if (reader.size() > 0) { reader.position(currentCursorReadPosition); reader.read(byteBufferSize); byteBufferSize.flip(); String s = new String(byteBufferSize.array()); if (!lookforSeparator(byteBufferSize.array())) { currentCursorReadPosition--; reverseSearchForSeparatorPosition(); } } } public List<SocialActivitiesEvent> readSocialEvents(Integer numberOfEvents) { List<SocialActivitiesEvent> events = new ArrayList<SocialActivitiesEvent>(); if (ioService.exists(path)) { for (int i = 0; i < numberOfEvents; i++) { try { String json = readNextSocialEventJSON(); SocialActivitiesEvent event = gson.fromJson(json, SocialActivitiesEvent.class); events.add(event); } catch (Exception e) { //ignore json error, try read next } } } return events; } private String readNextSocialEventJSON() throws IOException { if (startReading()) { prepareForReading(); } if (reader.size() <= 0) { throw new EmptySocialFile(); } reverseSearchForSeparatorPosition(); StringBuilder numberOfBytesNextJSON = getNumberOfBytesOfJSON(); if (StringUtils.isNumeric(numberOfBytesNextJSON.toString())) { return extractJSON(numberOfBytesNextJSON); } else { return readNextSocialEventJSON(); } } private String extractJSON( StringBuilder numberOfBytesNextJSON) throws IOException { Integer numberOfBytes = getNumberOfBytesToReadJSON(numberOfBytesNextJSON); putCursorInRightPosition(numberOfBytes); return readJSON(numberOfBytes); } private String readJSON(Integer numberOfBytes) throws IOException { ByteBuffer jsonByteBuffer = ByteBuffer.allocate(numberOfBytes.intValue()); StringBuilder extractedJSON = new StringBuilder(); while ((reader.read(jsonByteBuffer)) > 0) { extractedJSON.append(new String(jsonByteBuffer.array())); } return extractedJSON.toString(); } private void putCursorInRightPosition(Integer numberOfBytes) throws IOException { currentCursorReadPosition = currentCursorReadPosition - numberOfBytes.intValue(); reader.position(currentCursorReadPosition); } private Integer getNumberOfBytesToReadJSON(StringBuilder numberOfBytesNextJSON) { Integer numberOfBytes = new Integer(numberOfBytesNextJSON.toString()); return numberOfBytes; } private StringBuilder getNumberOfBytesOfJSON() throws IOException { currentCursorReadPosition = currentCursorReadPosition - 1; reader.position(currentCursorReadPosition); StringBuilder numberOfBytesNextJSON = new StringBuilder(); if (thereIsSomethingToRead(reader, byteBufferSize)) { byteBufferSize.flip(); if (!lookforSeparator(byteBufferSize.array())) { String charRead = new String(byteBufferSize.array()); numberOfBytesNextJSON.append(charRead); currentCursorReadPosition = currentCursorReadPosition - 1; reader.position(currentCursorReadPosition); while (thereIsSomethingToRead(reader, byteBufferSize)) { byteBufferSize.flip(); charRead = new String(byteBufferSize.array()); if (lookforSeparator(byteBufferSize.array())) { break; } numberOfBytesNextJSON.append(charRead); byteBufferSize.clear(); currentCursorReadPosition = currentCursorReadPosition - 1; reader.position(currentCursorReadPosition); } } } numberOfBytesNextJSON = numberOfBytesNextJSON.reverse(); return numberOfBytesNextJSON; } private boolean startReading() { return reader == null || currentCursorReadPosition <= 0; } private boolean thereIsSomethingToRead(SeekableByteChannel sbc, ByteBuffer bf) throws IOException { int i; return (i = sbc.read(bf)) > 0; } private boolean lookforSeparator(byte[] charRead) { String hexString = Hex.encodeHexString(charRead); return hexString.equalsIgnoreCase(JSON_SEPARATOR); } private class EmptySocialFile extends RuntimeException { } private class ErrorReadingFile extends RuntimeException { } }