1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.jsword.book.sword;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import org.crosswire.common.crypt.Sapphire;
29  import org.crosswire.jsword.JSMsg;
30  import org.crosswire.jsword.JSOtherMsg;
31  import org.crosswire.jsword.book.BookException;
32  import org.crosswire.jsword.book.BookMetaData;
33  import org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor;
34  import org.crosswire.jsword.book.sword.state.OpenFileState;
35  import org.crosswire.jsword.book.sword.state.OpenFileStateManager;
36  import org.crosswire.jsword.passage.Key;
37  import org.crosswire.jsword.passage.KeyUtil;
38  import org.crosswire.jsword.passage.Passage;
39  import org.crosswire.jsword.passage.RestrictionType;
40  import org.crosswire.jsword.passage.Verse;
41  import org.crosswire.jsword.passage.VerseRange;
42  import org.jdom2.Content;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  /**
47   * A generic way to read data from disk for later formatting.
48   *
49   * @param <T> The type of the OpenFileState that this class extends.
50   * @author Joe Walker
51   * @author DM Smith
52   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
53   */
54  public abstract class AbstractBackend<T extends OpenFileState> implements StatefulFileBackedBackend<T>, Backend<T> {
55  
56      /**
57       * Default constructor for the sake of serialization.
58       */
59      /* protected */
60      public AbstractBackend() {
61      }
62  
63      /**
64       * Construct a minimal backend
65       *
66       * @param sbmd
67       */
68      public AbstractBackend(SwordBookMetaData sbmd) {
69          bmd = sbmd;
70      }
71  
72      /* (non-Javadoc)
73       * @see org.crosswire.jsword.book.sword.Backend#getBookMetaData()
74       */
75      public BookMetaData getBookMetaData() {
76          return bmd;
77      }
78  
79      /* (non-Javadoc)
80       * @see org.crosswire.jsword.book.sword.Backend#decipher(byte[])
81       */
82      public void decipher(byte[] data) {
83          String cipherKeyString = getBookMetaData().getProperty(SwordBookMetaData.KEY_CIPHER_KEY);
84          if (cipherKeyString != null) {
85              Sapphire cipherEngine = new Sapphire(cipherKeyString.getBytes());
86              for (int i = 0; i < data.length; i++) {
87                  data[i] = cipherEngine.cipher(data[i]);
88              }
89              // destroy any evidence!
90              cipherEngine.burn();
91          }
92      }
93  
94      /* (non-Javadoc)
95       * @see org.crosswire.jsword.book.sword.Backend#encipher(byte[])
96       */
97      public void encipher(byte[] data) {
98          // Enciphering and deciphering are the same!
99          decipher(data);
100     }
101 
102     /* (non-Javadoc)
103      * @see org.crosswire.jsword.book.sword.Backend#readIndex()
104      */
105     public Key readIndex() {
106         // TODO(dms): Eliminate readIndex by deriving GenBookBackend from
107         // AbstractKeyBackend
108         return null;
109     }
110 
111     /* (non-Javadoc)
112      * @see org.crosswire.jsword.book.sword.Backend#contains(org.crosswire.jsword.passage.Key)
113      */
114     public abstract boolean contains(Key key);
115 
116     /* (non-Javadoc)
117      * @see org.crosswire.jsword.book.sword.Backend#getRawText(org.crosswire.jsword.passage.Key)
118      */
119     public String getRawText(Key key) throws BookException {
120         T state = null;
121         try {
122             state = initState();
123             return readRawContent(state, key);
124         } catch (IOException e) {
125             throw new BookException("Unable to obtain raw content from backend for key='" + key + '\'', e);
126         } finally {
127             OpenFileStateManager.instance().release(state);
128         }
129     }
130 
131     /* (non-Javadoc)
132      * @see org.crosswire.jsword.book.sword.Backend#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key)
133      */
134     public void setAliasKey(Key alias, Key source) throws BookException {
135         T state = null;
136         try {
137             state = initState();
138             setAliasKey(state, alias, source);
139         } catch (IOException e) {
140             throw new BookException(JSOtherMsg.lookupText("Unable to save {0}.", alias.getOsisID()));
141         } finally {
142             OpenFileStateManager.instance().release(state);
143         }
144     }
145 
146     /* (non-Javadoc)
147      * @see org.crosswire.jsword.book.sword.Backend#size(org.crosswire.jsword.passage.Key)
148      */
149     public int getRawTextLength(Key key) {
150         try {
151             String raw = getRawText(key);
152             return raw == null ? 0 : raw.length();
153         } catch (BookException e) {
154             return 0;
155         }
156     }
157 
158     /* (non-Javadoc)
159      * @see org.crosswire.jsword.book.sword.Backend#getGlobalKeyList()
160      */
161     public Key getGlobalKeyList() throws BookException {
162         //by default, this is not implemented
163         throw new UnsupportedOperationException("Fast global key list unsupported in this backend");
164     }
165 
166     /* (non-Javadoc)
167      * @see org.crosswire.jsword.book.sword.Backend#readToOsis(org.crosswire.jsword.passage.Key, org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor)
168      */
169     /* (non-Javadoc)
170      * @see org.crosswire.jsword.book.sword.AbstractBackend#getRawText(org.crosswire.jsword.passage.Key)
171      */
172     public List<Content> readToOsis(Key key, RawTextToXmlProcessor processor) throws BookException {
173 
174         final List<Content> content = new ArrayList<Content>();
175 
176         T openFileState = null;
177 
178         try {
179             openFileState = initState();
180             switch (this.bmd.getKeyType()) {
181                 case LIST:
182                     readNormalOsis(key, processor, content, openFileState);
183                     break;
184                 case TREE:
185                     readNormalOsisSingleKey(key, processor, content, openFileState);
186                     break;
187                 case VERSE:
188                     readPassageOsis(key, processor, content, openFileState);
189                     break;
190                 default:
191                     throw new BookException("Book has unsupported type of key");
192             }
193 
194             return content;
195         } finally {
196             OpenFileStateManager.instance().release(openFileState);
197         }
198     }
199 
200     private void readNormalOsis(Key key, RawTextToXmlProcessor processor, List<Content> content, T openFileState) throws BookException {
201         // simply lookup the key and process the relevant information
202         Iterator<Key> iterator = key.iterator();
203 
204         while (iterator.hasNext()) {
205             Key next = iterator.next();
206             String rawText;
207             try {
208                 rawText = readRawContent(openFileState, next);
209                 processor.postVerse(next, content, rawText);
210             } catch (IOException e) {
211                 // failed to process key 'next'
212                 throwFailedKeyException(key, next, e);
213             }
214         }
215     }
216 
217     /**
218      * Avoid using iterator for GenBook TreeKeys which would cause a GenBook nodes children to be appended to their parent 
219      * e.g. the top level page would include the whole book and result in OOM error 
220      */
221     private void readNormalOsisSingleKey(Key key, RawTextToXmlProcessor processor, List<Content> content, T openFileState) throws BookException {
222         String rawText;
223         try {
224             rawText = readRawContent(openFileState, key);
225             processor.postVerse(key, content, rawText);
226         } catch (IOException e) {
227             // failed to process key
228             throwFailedKeyException(key, key, e);
229         }
230     }
231 
232     /**
233      * Reads a passage as OSIS
234      *
235      * @param key           the given key
236      * @param processor     a processor for which to do things with
237      * @param content       a list of content to be appended to (i.e. the OSIS data)
238      * @param openFileState the open file state, from which we read things
239      * @throws BookException a book exception if we failed to read the book
240      */
241     private Verse readPassageOsis(Key key, RawTextToXmlProcessor processor, final List<Content> content, T openFileState) throws BookException {
242         Verse currentVerse = null;
243         final Passage ref = KeyUtil.getPassage(key);
244         final Iterator<VerseRange> rit = ref.rangeIterator(RestrictionType.CHAPTER);
245         while (rit.hasNext()) {
246             VerseRange range = rit.next();
247             processor.preRange(range, content);
248 
249             // FIXME(CJB): can this now be optimized since we can calculate
250             // the buffer size of what to read?
251             // now iterate through all verses in range
252             for (Key verseInRange : range) {
253                 currentVerse = KeyUtil.getVerse(verseInRange);
254                 try {
255                     String rawText = readRawContent(openFileState, currentVerse);
256                     processor.postVerse(verseInRange, content, rawText);
257                 } catch (IOException e) {
258                     //some versifications have more verses than modules contain - so can't throw
259                     //an error here...
260                     LOGGER.debug(e.getMessage(), e);
261                 }
262             }
263         }
264 
265         return currentVerse;
266     }
267 
268     /**
269      * If non-null, currentKey is used to throw the exception, other, masterKey
270      * is used instead, which will be more general.
271      *
272      * @param masterKey
273      *            the key containing currentKey
274      * @param currentKey
275      *            the currentKey
276      * @param e
277      *            the exception that occured
278      * @throws BookException
279      *             always thrown, a {@link BookException}
280      */
281     private void throwFailedKeyException(Key masterKey, Key currentKey, IOException e) throws BookException {
282         // TRANSLATOR: Common error condition: The file could not be read.
283         // There can be many reasons.
284         // {0} is a placeholder for the key.
285         if (currentKey == null) {
286             throw new BookException(JSMsg.gettext("Error reading {0}", masterKey.getName()), e);
287         }
288         throw new BookException(JSMsg.gettext("Error reading {0}", currentKey.getName()), e);
289     }
290 
291     /* (non-Javadoc)
292      * @see org.crosswire.jsword.book.sword.Backend#create()
293      */
294     public void create() throws IOException, BookException {
295         File dataPath = new File(SwordUtil.getExpandedDataPath(getBookMetaData()));
296         if (!dataPath.exists() && !dataPath.mkdirs()) {
297             throw new IOException("Unable to create module data path!");
298         }
299     }
300 
301     /* (non-Javadoc)
302      * @see org.crosswire.jsword.book.sword.Backend#isSupported()
303      */
304     public boolean isSupported() {
305         return true;
306     }
307 
308     /* (non-Javadoc)
309      * @see org.crosswire.jsword.book.sword.Backend#isWritable()
310      */
311     public boolean isWritable() {
312         return false;
313     }
314 
315     private SwordBookMetaData bmd;
316     private static final Logger LOGGER = LoggerFactory.getLogger(AbstractBackend.class);
317 }
318