/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/search/trunk/search-impl/impl/src/java/org/sakaiproject/search/index/impl/SegmentState.java $
* $Id: SegmentState.java 105078 2012-02-24 23:00:38Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.search.index.impl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.search.index.SegmentInfo;
/**
* @author ieb
*/
public class SegmentState
{
private static final String VERSION = "1.0";
private static final Log log = LogFactory.getLog(SegmentState.class);
private Map<String, FileRecord> fileRecords;
private long timeStamp = System.currentTimeMillis();
private String name;
/**
* @param timestampFile
* @throws IOException
* @throws IOException
*/
public SegmentState(SegmentInfo segInfo, File timestampFile) throws IOException
{
name = segInfo.getName();
if (timestampFile == null)
{
analyze(segInfo);
}
else
{
load(timestampFile);
}
}
/**
* @param timestampFile
* @throws IOException
*/
public void save(File checksumFile) throws IOException
{
File tmpFile = new File(checksumFile.getAbsolutePath() + ".tmp");
FileWriter fw = null;
try {
fw = new FileWriter(tmpFile);
fw.append(VERSION).append("\n");
fw.append(String.valueOf(timeStamp)).append("\n");
for (Iterator<FileRecord> i = fileRecords.values().iterator(); i.hasNext();)
{
FileRecord fr = i.next();
fw.append(fr.path).append(";");
fw.append(fr.checksum).append(";");
fw.append(String.valueOf(fr.length)).append(";");
fw.append(String.valueOf(fr.lastMod)).append(";\n");
}
} catch (IOException e) {
throw new IOException();
}
finally {
if (fw != null) {
fw.close();
}
}
if (!tmpFile.renameTo(checksumFile))
{
log.warn("unable to rename " + tmpFile.getPath() + " to " + checksumFile.getPath());
}
}
/**
* @param timestampFile
* @throws IOException
*/
private void load(File checksumFile) throws IOException
{
fileRecords = new HashMap<String, FileRecord>();
BufferedReader fr = new BufferedReader(new FileReader(checksumFile));
String version = fr.readLine();
if (VERSION.equals(version))
{
String ts = fr.readLine();
timeStamp = Long.parseLong(ts);
for (String line = fr.readLine(); line != null; line = fr.readLine())
{
String[] elements = line.split(";");
FileRecord infr = new FileRecord();
infr.path = elements[0];
infr.checksum = elements[1];
infr.length = Long.parseLong(elements[2]);
infr.lastMod = Long.parseLong(elements[3]);
fileRecords.put(infr.path, infr);
}
}
else
{
log.warn("Segment (" + name + "): Unrecognized version number " + version);
}
fr.close();
}
/**
* @param segInfo
*/
public void analyze(SegmentInfo segInfo)
{
File[] files = segInfo.getSegmentLocation().listFiles();
String basePath = segInfo.getSegmentLocation().getAbsolutePath();
fileRecords = new HashMap<String, FileRecord>();
MessageDigest md5 = null;
try
{
md5 = MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e)
{
log.error("Segment (" + name + "): MD5 not available ", e);
}
byte[] buffer = new byte[4096];
if (files != null)
{
for (int i = 0; i < files.length; i++)
{
try
{
String echecksum = "none";
if (md5 != null)
{
InputStream fin = new FileInputStream(files[i]);
int len = 0;
md5.reset();
while ((len = fin.read(buffer)) > 0)
{
md5.update(buffer, 0, len);
}
fin.close();
char[] encoding = "0123456789ABCDEF".toCharArray();
byte[] checksum = md5.digest();
char[] hexchecksum = new char[checksum.length * 2];
for (int j = 0; j < checksum.length; j++)
{
int lo = checksum[j] & 0x0f;
int hi = (checksum[j] >> 4) & 0x0f;
hexchecksum[j * 2] = encoding[lo];
hexchecksum[j * 2 + 1] = encoding[hi];
}
echecksum = new String(hexchecksum);
}
FileRecord fr = new FileRecord();
fr.checksum = echecksum;
fr.path = files[i].getAbsolutePath().substring(basePath.length());
fr.lastMod = files[i].lastModified();
fr.length = files[i].length();
fileRecords.put(fr.path, fr);
}
catch (Exception ex)
{
log.error("Segment (" + name + "): Failed to generate checksum of "
+ files[i].getAbsolutePath(), ex);
}
}
}
}
public class FileRecord
{
public long length;
public long lastMod;
public String path;
public String checksum = "none";
/**
* @param sfr
* @return
*/
public String diff(FileRecord sfr)
{
StringBuilder sb = new StringBuilder();
if (sfr == null)
{
return "new file";
}
if (!path.equals(sfr.path))
{
return "[not the same file]";
}
int mod = 0;
if (!checksum.equals(sfr.checksum))
{
sb.append("content changed,");
mod++;
}
if (lastMod > sfr.lastMod)
{
sb.append("newer;");
mod++;
}
else if (lastMod < sfr.lastMod)
{
sb.append("older;");
mod++;
}
else
{
sb.append("same age;");
}
if (length > sfr.length)
{
sb.append("larger;");
mod++;
}
else if (length < sfr.length)
{
sb.append("smaller;");
mod++;
}
else
{
sb.append("same size;");
}
if (mod != 0)
{
return sb.toString();
}
return "identical";
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return path + ";" + new Date(lastMod) + ";" + length + ";";
}
}
/**
* @return the timeStamp
*/
public long getTimeStamp()
{
return timeStamp;
}
/**
* @param timeStamp
* the timeStamp to set
*/
public void setTimeStamp(long timeStamp)
{
this.timeStamp = timeStamp;
}
/**
* Check the validity of this segment against the stored segment
*
* @param message
* @param logging
* @param storedSegmentState
*/
public boolean checkValidity(boolean logging, String message,
SegmentState storedSegmentState)
{
if (storedSegmentState == null)
{
if (logging)
{
log
.info("Segment ("
+ name
+ "): The segment has no stored state, it may be new or it could be dammaged ");
}
return true;
}
StringBuilder sb = new StringBuilder();
if (timeStamp > storedSegmentState.getTimeStamp())
{
sb.append(" This Segment has been modified ").append(name).append("\n");
}
for (Iterator<FileRecord> i = fileRecords.values().iterator(); i.hasNext();)
{
FileRecord fr = i.next();
FileRecord sfr = storedSegmentState.getFileRecord(fr.path);
String differences = fr.diff(sfr);
sb.append(" Checking [").append(fr).append("]==[").append(sfr).append("] ")
.append(differences).append("\n");
}
for (Iterator<FileRecord> i = storedSegmentState.iterator(); i.hasNext();)
{
FileRecord sfr = i.next();
FileRecord fr = fileRecords.get(sfr.path);
if (fr == null)
{
sb.append(" Dropped ").append("").append("\n");
}
}
if (logging)
{
log.info("Segment (" + name + "): Checked " + name + "\n" + sb.toString());
}
return true;
}
/**
* @return
*/
private Iterator<FileRecord> iterator()
{
return fileRecords.values().iterator();
}
/**
* @param path
* @return
*/
private FileRecord getFileRecord(String path)
{
return fileRecords.get(path);
}
}