/* * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.ext.backup.impl; import org.exoplatform.commons.utils.PrivilegedFileHelper; import org.exoplatform.commons.utils.PrivilegedSystemHelper; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.dataflow.TransactionChangesLog; import org.exoplatform.services.jcr.dataflow.persistent.PersistedPropertyData; import org.exoplatform.services.jcr.datamodel.ItemData; import org.exoplatform.services.jcr.datamodel.ValueData; import org.exoplatform.services.jcr.impl.dataflow.SpoolConfig; import org.exoplatform.services.jcr.impl.dataflow.persistent.StreamPersistedValueData; import org.exoplatform.services.jcr.impl.util.io.FileCleaner; import org.exoplatform.services.jcr.impl.util.io.SpoolFile; import org.exoplatform.services.jcr.util.IdGenerator; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.List; /** * Created by The eXo Platform SAS. * * @author <a href="mailto:alex.reshetnyak@exoplatform.com.ua">Alex Reshetnyak</a> * @version $Id: PendingChangesLog.java 31768 2009-05-14 09:35:43Z pnedonosko $ */ public class PendingChangesLog { /** * The apache logger. */ private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.ext.PendingChangesLog"); /** * The definition of ChangesLog types. */ public final class Type { /** * CHANGESLOG_WITHOUT_STREAM. The the type fore ChangesLog without streams. */ public static final int CHANGESLOG_WITHOUT_STREAM = 1; /** * CHANGESLOG_WITH_STREAM. The the type fore ChangesLog with streams. */ public static final int CHANGESLOG_WITH_STREAM = 2; /** * Empty constructor. */ private Type() { } } /** * Minimal sleep timeout. */ private static final int SLEEP_TIME = 5; /** * ChangesLog with data. */ private TransactionChangesLog itemDataChangesLog; /** * The list of input streams who are contains in ChangesLog. */ private List<InputStream> listInputStream; /** * The list of RandomAccessFiles who ate contains in ChangesLog. */ private List<RandomAccessFile> listRandomAccessFile; /** * Type of ChangesLog (CHANGESLOG_WITHOUT_STREAM or CHANGESLOG_WITH_STREAM). */ private int containerType; /** * The list of FixupStreams who are indicate the location the input streams in ChangesLog. */ private List<FixupStream> listFixupStream; // private HashMap<FixupStream, RandomAccessFile> mapFixupStream; /** * The list of Files who are contains in ChangesLog. */ private List<SpoolFile> listFile; /** * The identification string for PendingChangesLog. */ private String identifier; /** * The FileCleaner will delete the temporary files. */ private FileCleaner fileCleaner; /** * The arrays of bytes for serialized ChangesLog without streams. */ private byte[] data; /** * Temporary directory; */ private final File tempDir; /** * PendingChangesLog constructor. * * @param itemDataChangesLog * ChangesLog with data * @param fileCleaner * the FileCleaner * @throws IOException * will be generated the IOExaption */ public PendingChangesLog(TransactionChangesLog itemDataChangesLog, FileCleaner fileCleaner) throws IOException { this.itemDataChangesLog = itemDataChangesLog; listInputStream = new ArrayList<InputStream>(); listFixupStream = new ArrayList<FixupStream>(); containerType = analysisItemDataChangesLog(); listFile = new ArrayList<SpoolFile>(); identifier = IdGenerator.generate(); this.fileCleaner = fileCleaner; this.tempDir = new File(PrivilegedSystemHelper.getProperty("java.io.tmpdir")); } /** * PendingChangesLog constructor. * * @param itemDataChangesLog * ChangesLog with data * @param identifier * identifier to this PendingChangesLog. * @param type * type of PendingChangesLog * @param fileCleaner * the FileCleaner * @throws IOException * will be generated the IOExaption */ public PendingChangesLog(TransactionChangesLog itemDataChangesLog, String identifier, int type, FileCleaner fileCleaner) throws IOException { this.itemDataChangesLog = itemDataChangesLog; listInputStream = new ArrayList<InputStream>(); listFixupStream = new ArrayList<FixupStream>(); listRandomAccessFile = new ArrayList<RandomAccessFile>(); listFile = new ArrayList<SpoolFile>(); this.identifier = identifier; containerType = type; this.fileCleaner = fileCleaner; this.tempDir = new File(PrivilegedSystemHelper.getProperty("java.io.tmpdir")); } /** * PendingChangesLog constructor. * * @param identifier * identifier to this PendingChangesLog. * @param dataLength * the length of binary data */ public PendingChangesLog(String identifier, int dataLength) { this.identifier = identifier; data = new byte[dataLength]; this.tempDir = new File(PrivilegedSystemHelper.getProperty("java.io.tmpdir")); } /** * PendingChangesLog constructor. * * @param transactionChangesLog * ChangesLog with data * @param listFixupStreams * list of FixupStreams * @param listFiles * list of Files * @param fileCleaner * the FileCleaner */ public PendingChangesLog(TransactionChangesLog transactionChangesLog, List<FixupStream> listFixupStreams, List<SpoolFile> listFiles, FileCleaner fileCleaner) { this.itemDataChangesLog = transactionChangesLog; this.listFixupStream = listFixupStreams; this.listFile = listFiles; this.fileCleaner = fileCleaner; this.tempDir = new File(PrivilegedSystemHelper.getProperty("java.io.tmpdir")); } /** * putData. * * @param offset * offset in 'data' * @param tempData * piece of binary data */ public void putData(int offset, byte[] tempData) { for (int i = 0; i < tempData.length; i++) data[i + offset] = tempData[i]; } /** * getData. * * @return byte[] return the binary data */ public byte[] getData() { return data; } /** * getItemDataChangesLog. * * @return TransactionChangesLog return the ChangesLog */ public TransactionChangesLog getItemDataChangesLog() { return itemDataChangesLog; } /** * getInputStreams. * * @return List return the list of input streams */ public List<InputStream> getInputStreams() { return listInputStream; } /** * getListRandomAccessFiles. * * @return List return the list of RandomAccessFiles */ public List<RandomAccessFile> getListRandomAccessFiles() { return listRandomAccessFile; } /** * getListFile. * * @return List return list of Files */ public List<SpoolFile> getListFile() { return listFile; } /** * getFixupStreams. * * @return List return list of FixupStreams */ public List<FixupStream> getFixupStreams() { return listFixupStream; } /** * analysisItemDataChangesLog. * * @return int type of ChangesLog (CHANGESLOG_WITHOUT_STREAM or CHANGESLOG_WITH_STREAM) * @throws IOException * will be generated the IOException */ private int analysisItemDataChangesLog() throws IOException { int itemDataChangesLogType = PendingChangesLog.Type.CHANGESLOG_WITHOUT_STREAM; int i = 0; for (ItemState itemState : itemDataChangesLog.getAllStates()) { ItemData itemData = itemState.getData(); if (itemData instanceof PersistedPropertyData) { PersistedPropertyData propertyData = (PersistedPropertyData)itemData; if ((propertyData.getValues() != null)) for (int j = 0; j < propertyData.getValues().size(); j++) if (!(propertyData.getValues().get(j).isByteArray())) { listFixupStream.add(new FixupStream(i, j)); InputStream inputStream; if (itemState.isDeleted()) inputStream = new ByteArrayInputStream("".getBytes()); else inputStream = propertyData.getValues().get(j).getAsStream(); listInputStream.add(inputStream); itemDataChangesLogType = PendingChangesLog.Type.CHANGESLOG_WITH_STREAM; } } i++; } return itemDataChangesLogType; } /** * getConteinerType. * * @return int return the type of ChangesLog */ public int getConteinerType() { return containerType; } /** * getIdentifier. * * @return String return the identifier string */ public String getIdentifier() { return identifier; } /** * getAsByteArray. Make the array of bytes from ChangesLog. * * @param dataChangesLog * the ChangesLog with data * @return byte[] return the serialized ChangesLog * @throws IOException * will be generated the IOException */ public static byte[] getAsByteArray(TransactionChangesLog dataChangesLog) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(dataChangesLog); byte[] bArray = os.toByteArray(); return bArray; } /** * getAsItemDataChangesLog. Make the ChangesLog from array of bytes. * * @param byteArray * the serialized ChangesLog * @return TransactionChangesLog return the deserialized ChangesLog * @throws IOException * will be generated the IOException * @throws ClassNotFoundException * will be generated the ClassNotFoundException */ public static TransactionChangesLog getAsItemDataChangesLog(byte[] byteArray) throws IOException, ClassNotFoundException { ByteArrayInputStream is = new ByteArrayInputStream(byteArray); ObjectInputStream ois = new ObjectInputStream(is); TransactionChangesLog objRead = (TransactionChangesLog)ois.readObject(); return objRead; } /** * getRandomAccessFile. * * @param fs * the FixupStream * @return RandomAccessFile return the RandomAccessFile by FixupStream * @throws IOException * will be generated the IOException */ public RandomAccessFile getRandomAccessFile(FixupStream fs) throws IOException { int i = 0; try { for (i = 0; i < listFixupStream.size(); i++) if (this.listFixupStream.get(i).compare(fs)) return listRandomAccessFile.get(i); } catch (IndexOutOfBoundsException e) { try { Thread.sleep(SLEEP_TIME); return listRandomAccessFile.get(i); } catch (InterruptedException ie) { LOG.error("The interrupted exceptio : ", ie); } catch (IndexOutOfBoundsException ioobe) { if (LOG.isDebugEnabled()) { LOG.info("listFixupStream.size() == " + listFixupStream.size()); LOG.info("listRandomAccessFile.size() == " + listRandomAccessFile.size()); LOG.info(" i == " + i); } synchronized (this) { if (listFile.size() > i) { listFile.remove(i); } listFixupStream.remove(i); addNewStream(fs); getRandomAccessFile(fs); } } } return null; } /** * addNewStream. * * @param fs * the FixupStream * @throws IOException * will be generated the IOException */ public void addNewStream(FixupStream fs) throws IOException { this.getFixupStreams().add(fs); SpoolFile f = SpoolFile.createTempFile("tempFile" + IdGenerator.generate(), ".tmp", tempDir); this.getListFile().add(f); this.getListRandomAccessFiles().add(PrivilegedFileHelper.randomAccessFile(f, "rw")); } /** * Restore ChangesLog(set the InputStreams to ValueData). * * @throws IOException * will be generated the IOException */ public void restore() throws IOException { SpoolConfig spoolConfig = new SpoolConfig(fileCleaner); List<ItemState> listItemState = itemDataChangesLog.getAllStates(); for (int i = 0; i < this.listFixupStream.size(); i++) { ItemState itemState = listItemState.get(listFixupStream.get(i).getItemSateId()); ItemData itemData = itemState.getData(); PersistedPropertyData propertyData = (PersistedPropertyData)itemData; ValueData vd = (propertyData.getValues().get(listFixupStream.get(i).getValueDataId())); // re-init the value propertyData.getValues().set(listFixupStream.get(i).getValueDataId(), new StreamPersistedValueData(vd.getOrderNumber(), listFile.get(i), spoolConfig)); } if (listRandomAccessFile != null) for (int i = 0; i < listRandomAccessFile.size(); i++) listRandomAccessFile.get(i).close(); for (int i = 0; i < listFile.size(); i++) fileCleaner.addFile(listFile.get(i)); } }