/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * 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 net.java.sip.communicator.impl.history; import java.io.*; import java.security.*; import java.util.*; import net.java.sip.communicator.service.history.*; import net.java.sip.communicator.service.history.records.*; import net.java.sip.communicator.util.*; import org.jitsi.util.xml.XMLUtils; import org.w3c.dom.*; /** * @author Alexander Pelov * @author Yana Stamcheva */ public class HistoryImpl implements History { private static Logger log = Logger.getLogger(HistoryImpl.class); /** * The supported filetype. */ public static final String SUPPORTED_FILETYPE = "xml"; private HistoryID id; private HistoryRecordStructure historyRecordStructure; private HistoryServiceImpl historyServiceImpl; private File directory; private HistoryReader reader; /** * The <tt>InteractiveHistoryReader</tt>. */ private InteractiveHistoryReader interactiveReader; private HistoryWriter writer; private SortedMap<String, Object> historyDocuments = new TreeMap<String, Object>(); /** * Creates an instance of <tt>HistoryImpl</tt> by specifying the history * identifier, the directory, the <tt>HistoryRecordStructure</tt> to use * and the parent <tt>HistoryServiceImpl</tt>. * @param id the identifier * @param directory the directory * @param historyRecordStructure the structure * @param historyServiceImpl the parent history service */ protected HistoryImpl(HistoryID id, File directory, HistoryRecordStructure historyRecordStructure, HistoryServiceImpl historyServiceImpl) { try { log.logEntry(); // TODO: Assert: Assert.assertNonNull(historyServiceImpl, "The // historyServiceImpl should be non-null."); // TODO: Assert: Assert.assertNonNull(id, "The ID should be // non-null."); // TODO: Assert: Assert.assertNonNull(historyRecordStructure, "The // structure should be non-null."); this.id = id; this.directory = directory; this.historyServiceImpl = historyServiceImpl; this.historyRecordStructure = historyRecordStructure; this.reader = null; this.writer = null; this.reloadDocumentList(); } finally { log.logExit(); } } /** * Returns the identifier of this history. * @return the identifier of this history */ public HistoryID getID() { return this.id; } /** * Returns the current <tt>HistoryRecordStructure</tt>. * @return the current <tt>HistoryRecordStructure</tt> */ public HistoryRecordStructure getHistoryRecordsStructure() { return this.historyRecordStructure; } /** * Sets the given <tt>structure</tt> to be the new history records * structure used in this history implementation. * @param structure the new <tt>HistoryRecordStructure</tt> to use */ public void setHistoryRecordsStructure(HistoryRecordStructure structure) { this.historyRecordStructure = structure; try { File dbDatFile = new File(directory, HistoryServiceImpl.DATA_FILE); DBStructSerializer dbss = new DBStructSerializer(historyServiceImpl); dbss.writeHistory(dbDatFile, this); } catch (IOException e) { log.debug("Could not create new history structure"); } } public HistoryReader getReader() { if (this.reader == null) { this.reader = new HistoryReaderImpl(this); } return this.reader; } /** * Returns an object that can be used to read and query this history. The * <tt>InteractiveHistoryReader</tt> differs from the <tt>HistoryReader</tt> * in the way it manages query results. It allows to cancel a search at * any time and to track history results through a * <tt>HistoryQueryListener</tt>. * @return an object that can be used to read and query this history */ public InteractiveHistoryReader getInteractiveReader() { if (interactiveReader == null) interactiveReader = new InteractiveHistoryReaderImpl(this); return interactiveReader; } public HistoryWriter getWriter() { if (writer == null) writer = new HistoryWriterImpl(this); return writer; } protected HistoryServiceImpl getHistoryServiceImpl() { return this.historyServiceImpl; } void reloadDocumentList() { synchronized (this.historyDocuments) { this.historyDocuments.clear(); File[] files = this.directory.listFiles(); // TODO: Assert: Assert.assertNonNull(files, "The list of files // should be non-null."); for (int i = 0; i < files.length; i++) { if (!files[i].isDirectory()) { String filename = files[i].getName(); if (filename.endsWith(SUPPORTED_FILETYPE)) { this.historyDocuments.put(filename, files[i]); } } } } } protected Document createDocument(String filename) { Document retVal = null; synchronized (this.historyDocuments) { if (this.historyDocuments.containsKey(filename)) { retVal = getDocumentForFile(filename); } else { retVal = this.historyServiceImpl.getDocumentBuilder() .newDocument(); retVal.appendChild(retVal.createElement("history")); this.historyDocuments.put(filename, retVal); } } return retVal; } protected void writeFile(String filename) throws InvalidParameterException, IOException { File file = new File(this.directory, filename); synchronized (this.historyDocuments) { if (!this.historyDocuments.containsKey(filename)) { throw new InvalidParameterException("The requested " + "filename does not exist in the document list."); } Object obj = this.historyDocuments.get(filename); if (obj instanceof Document) { Document doc = (Document) obj; synchronized (doc) { XMLUtils.writeXML(doc, file); } } } } protected void writeFile(String filename, Document doc) throws InvalidParameterException, IOException { File file = new File(this.directory, filename); synchronized (this.historyDocuments) { if (!this.historyDocuments.containsKey(filename)) { throw new InvalidParameterException("The requested " + "filename does not exist in the document list."); } synchronized (doc) { XMLUtils.writeXML(doc, file); } } } protected Iterator<String> getFileList() { return this.historyDocuments.keySet().iterator(); } protected Document getDocumentForFile(String filename) throws InvalidParameterException, RuntimeException { Document retVal = null; synchronized (this.historyDocuments) { if (!this.historyDocuments.containsKey(filename)) { throw new InvalidParameterException("The requested " + "filename does not exist in the document list."); } Object obj = this.historyDocuments.get(filename); if (obj instanceof Document) { // Document already loaded. Use it directly retVal = (Document) obj; } else if (obj instanceof File) { File file = (File) obj; try { retVal = this.historyServiceImpl.parse(file); } catch (Exception e) { // throw new RuntimeException("Error occured while " // + "parsing XML document.", e); // log.error("Error occured while parsing XML document.", e); log.error("Error occured while parsing XML document.", e); // will try to fix the xml file retVal = getFixedDocument(file); // if is not fixed return if(retVal == null) return null; } // Cache the loaded document for reuse if configured if(historyServiceImpl.isCacheEnabled()) this.historyDocuments.put(filename, retVal); } else { // TODO: Assert: Assert.fail("Internal error - the data type " + // "should be either Document or File."); } } return retVal; } /** * Methods trying to fix histry xml files if corrupted */ /** * Returns the fixed document as xml Document * if file cannot be fixed return null * * @param file File the file trying to fix * @return Document the fixed doc */ public Document getFixedDocument(File file) { log.info("Will try to fix file : " + file); StringBuffer resultDocStr = new StringBuffer("<history>"); try { BufferedReader inReader = new BufferedReader(new FileReader(file)); String line = null; while ( (line = inReader.readLine()) != null) { // find the next start of record node if (line.indexOf("<record") == -1) { continue; } String record = getRecordNodeString(line, inReader).toString(); if (record != null && isValidXML(record)) { resultDocStr.append(record); } } } catch (Exception ex1) { log.error("File cannot be fixed. Erro reading! " + ex1.getLocalizedMessage()); } resultDocStr.append("</history>"); try { Document result = this.historyServiceImpl.parse(new ByteArrayInputStream( resultDocStr.toString().getBytes("UTF-8"))); // parsing is ok . lets overwrite with correct values log.trace("File fixed will write to disk!"); XMLUtils.writeXML(result, file); return result; } catch (Exception ex) { System.out.println("again cannot parse " + ex.getMessage()); return null; } } /** * Returns the string containing the record node from the xml - * the supplied Reader * @param startingLine String * @param inReader BufferedReader * @return StringBuffer */ private StringBuffer getRecordNodeString( String startingLine, BufferedReader inReader) { try { StringBuffer result = new StringBuffer(startingLine); String line = null; while ( (line = inReader.readLine()) != null) { // find the next start of record node if (line.indexOf("</record>") != -1) { result.append(line); break; } result.append(line); } return result; } catch (IOException ex) { log.info("Error reading record " + ex.getLocalizedMessage()); return null; } } /** * Checks whether the given xml is valid * @param str String * @return boolean */ private boolean isValidXML(String str) { try { this.historyServiceImpl.parse( new ByteArrayInputStream(str.getBytes("UTF-8"))); } catch (Exception ex) { log.error("not valid xml " + str + " " + ex.getMessage()); return false; } return true; } }