2014-10-27 9 views
1

У меня есть несколько процессов на разных компьютерах, которые необходимы для чтения/записи в общий файл XML, для этого я использую DOM with Java и FileLocks (Хотя я знаю, что база данных будет более эффективным подходом , это небезопасно из-за ограничений проекта).Одновременно пишите в файл XML

Чтобы внести изменения в файл XML, соответствующий процесс сначала создает исключительно заблокированный канал, который используется для чтения файла, затем он пытается повторно использовать один и тот же канал для записи новой версии перед закрытием канала; таким образом, замок никогда не падает. Однако проблема заключается в том, что я получаю java.nio.channels.ClosedChannelException при попытке записать результат, хотя я никогда не закрываю канал напрямую. У меня есть подозрения, что строка кода:

doc = dBuilder.parse(Channels.newInputStream(channel)); 

закрывает канал. Если да, то как я могу заставить канал оставаться открытым? Мой код можно увидеть ниже:

[удален код после обновления]

UPDATE: Размещение System.out.println(channel.isOpen()) до и после подозрительного строки кода подтверждает, что это, где канал закрыт.

ОБНОВЛЕНИЕ: После запроса separate question приведенный ниже код предотвращает закрытие канала во время операции синтаксического анализа. Проблема заключается в том, что вместо замены исходного XML-файла трансформатор добавляет измененный документ в оригинал. В документации я не могу найти никаких связанных опций для указания вывода Transformer.transform (я искал Transformer/Transformer factory/StreamResult). Я что-то упускаю? Нужно ли мне как-то очистить канал перед тем, как писать? Благодарю.

ОБНОВЛЕНИЕ: Наконец-то решена проблема с добавлением, обрезая канал до размера 0. Спасибо @JLRishe за совет. Выложили рабочий код в качестве ответа.

ответ

0

Это код, который, в конце концов, работает! См. Обновления вопросов для объяснения разных частей.

import java.io.*; 
import java.nio.channels.*; 

import javax.xml.parsers.*; 
import javax.xml.transform.*; 
import javax.xml.transform.dom.DOMSource; 
import javax.xml.transform.stream.StreamResult; 
import javax.xml.xpath.*; 

import org.w3c.dom.*; 
import org.xml.sax.SAXException; 

public class Test2{ 
    String path = "...Test 2.xml"; 

    public Test2(){ 
     Document doc = null; 
     DocumentBuilderFactory dbFactory; 
     DocumentBuilder dBuilder; 
     NodeList itemList; 
     Transformer transformer; 
     FileChannel channel; 
     Element newElement; 
     int prevNumber; 
     TransformerFactory transformerFactory ; 
     DOMSource source; 
     StreamResult result; 
     NonClosingInputStream ncis = null; 
     try { 
      channel = new RandomAccessFile(new File(path), "rw").getChannel(); 
      FileLock lock = channel.lock(0L, Long.MAX_VALUE, false); 

      try { 
       dbFactory = DocumentBuilderFactory.newInstance(); 
       dBuilder = dbFactory.newDocumentBuilder(); 
       ncis = new NonClosingInputStream(Channels.newInputStream(channel)); 
       doc = dBuilder.parse(ncis); 
      } catch (SAXException | IOException | ParserConfigurationException e) { 
       e.printStackTrace(); 
      } 
      doc.getDocumentElement().normalize(); 
      itemList = doc.getElementsByTagName("Item"); 
      newElement = doc.createElement("Item"); 
      prevNumber = Integer.parseInt(((Element) itemList.item(itemList.getLength() - 1)).getAttribute("Number")); 
      newElement.setAttribute("Number", (prevNumber + 1) + ""); 

      doc.getDocumentElement().appendChild(newElement); 

      transformerFactory = TransformerFactory.newInstance(); 
      transformer = transformerFactory.newTransformer(); 
      source = new DOMSource(doc); 
      channel.truncate(0); 
      result = new StreamResult(Channels.newOutputStream(channel)); 

      transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); 
      transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); 
      transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 
      transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 
      transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 
      transformer.transform(source, result); 
      channel.close(); 
     } catch (IOException | TransformerException e) { 
      e.printStackTrace(); 
     } finally { 
      try { 
       ncis.reallyClose(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    class NonClosingInputStream extends FilterInputStream { 

     public NonClosingInputStream(InputStream it) { 
      super(it); 
     } 

     @Override 
     public void close() throws IOException { 
      // Do nothing. 
     } 

     public void reallyClose() throws IOException { 
      // Actually close. 
      in.close(); 
     } 
    } 

    public static void main(String[] args){ 
     new Test2(); 
    } 
} 
0

Попробуйте эту конструкцию вместо:

  1. Создать новую услугу (процесс), которая открывает сокет и прослушивает «обновление команды».
  2. Все другие процессы не записывать в файл напрямую, но вместо того, чтобы отправить «команды обновления» на новый сервис

Таким образом, вам не нужно беспокоиться о блокировке. Чтобы сделать все более надежным, вы можете добавить буферы к процессам отправки, чтобы они могли продолжать жить некоторое время, когда служба не работает.

При таком подходе вам никогда не придется иметь дело с файловыми замками (которые могут быть ненадежными в зависимости от вашей ОС). В сокете также убедитесь, что вы не можете запустить службу дважды.

+0

Привет, Аарон, это звучит как лучшее решение, и я могу заглянуть в будущее. Пока, хотя проект довольно небольшой, поэтому я бы предпочел заставить его работать так, как есть. – Hungry

+0

Попробуйте перемотать канал после прочтения.Или попробуйте придумать небольшой тестовый пример, где вы просто читаете простую строку; для вашего теста нет необходимости добавлять сложность, которую приносит XML. Если малый тест работает, то в «DocumentBuilderFactory» есть ошибка/проблема, или, может быть, возвращаемый 'InputStream' имеет метод' finalize() '. –

+0

Приветствия, обновили вопрос на основе вашего ответа. – Hungry

Смежные вопросы