DirectoryRosterStore.java

  1. /**
  2.  *
  3.  * Copyright 2013 the original author or authors
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.roster.rosterstore;

  18. import java.io.File;
  19. import java.io.FileFilter;
  20. import java.io.IOException;
  21. import java.io.StringReader;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.List;
  25. import java.util.logging.Level;
  26. import java.util.logging.Logger;

  27. import org.jivesoftware.smack.roster.packet.RosterPacket;
  28. import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
  29. import org.jivesoftware.smack.util.FileUtils;
  30. import org.jivesoftware.smack.util.XmlStringBuilder;
  31. import org.jivesoftware.smack.util.stringencoder.Base32;
  32. import org.jxmpp.jid.Jid;
  33. import org.jxmpp.jid.impl.JidCreate;
  34. import org.xmlpull.v1.XmlPullParserFactory;
  35. import org.xmlpull.v1.XmlPullParser;
  36. import org.xmlpull.v1.XmlPullParserException;

  37. /**
  38.  * Stores roster entries as specified by RFC 6121 for roster versioning
  39.  * in a set of files.
  40.  *
  41.  * @author Lars Noschinski
  42.  * @author Fabian Schuetz
  43.  */
  44. public class DirectoryRosterStore implements RosterStore {

  45.     private final File fileDir;

  46.     private static final String ENTRY_PREFIX = "entry-";
  47.     private static final String VERSION_FILE_NAME = "__version__";
  48.     private static final String STORE_ID = "DEFAULT_ROSTER_STORE";
  49.     private static final Logger LOGGER = Logger.getLogger(DirectoryRosterStore.class.getName());

  50.     private static final FileFilter rosterDirFilter = new FileFilter() {

  51.         @Override
  52.         public boolean accept(File file) {
  53.             String name = file.getName();
  54.             return name.startsWith(ENTRY_PREFIX);
  55.         }

  56.     };

  57.     /**
  58.      * @param baseDir
  59.      *            will be the directory where all roster entries are stored. One
  60.      *            file for each entry, such that file.name = entry.username.
  61.      *            There is also one special file '__version__' that contains the
  62.      *            current version string.
  63.      */
  64.     private DirectoryRosterStore(final File baseDir) {
  65.         this.fileDir = baseDir;
  66.     }

  67.     /**
  68.      * Creates a new roster store on disk
  69.      *
  70.      * @param baseDir
  71.      *            The directory to create the store in. The directory should
  72.      *            be empty
  73.      * @return A {@link DirectoryRosterStore} instance if successful,
  74.      *         <code>null</code> else.
  75.      */
  76.     public static DirectoryRosterStore init(final File baseDir) {
  77.         DirectoryRosterStore store = new DirectoryRosterStore(baseDir);
  78.         if (store.setRosterVersion("")) {
  79.             return store;
  80.         }
  81.         else {
  82.             return null;
  83.         }
  84.     }

  85.     /**
  86.      * Opens a roster store
  87.      * @param baseDir
  88.      *            The directory containing the roster store.
  89.      * @return A {@link DirectoryRosterStore} instance if successful,
  90.      *         <code>null</code> else.
  91.      */
  92.     public static DirectoryRosterStore open(final File baseDir) {
  93.         DirectoryRosterStore store = new DirectoryRosterStore(baseDir);
  94.         String s = FileUtils.readFile(store.getVersionFile());
  95.         if (s != null && s.startsWith(STORE_ID + "\n")) {
  96.             return store;
  97.         }
  98.         else {
  99.             return null;
  100.         }
  101.     }

  102.     private File getVersionFile() {
  103.         return new File(fileDir, VERSION_FILE_NAME);
  104.     }

  105.     @Override
  106.     public List<Item> getEntries() {
  107.         List<Item> entries = new ArrayList<RosterPacket.Item>();

  108.         for (File file : fileDir.listFiles(rosterDirFilter)) {
  109.             Item entry = readEntry(file);
  110.             if (entry == null) {
  111.                 log("Roster store file '" + file + "' is invalid.");
  112.             }
  113.             else {
  114.                 entries.add(entry);
  115.             }
  116.         }

  117.         return entries;
  118.     }

  119.     @Override
  120.     public Item getEntry(Jid bareJid) {
  121.         return readEntry(getBareJidFile(bareJid));
  122.     }

  123.     @Override
  124.     public String getRosterVersion() {
  125.         String s = FileUtils.readFile(getVersionFile());
  126.         if (s == null) {
  127.             return null;
  128.         }
  129.         String[] lines = s.split("\n", 2);
  130.         if (lines.length < 2) {
  131.             return null;
  132.         }
  133.         return lines[1];
  134.     }

  135.     private boolean setRosterVersion(String version) {
  136.         return FileUtils.writeFile(getVersionFile(), STORE_ID + "\n" + version);
  137.     }

  138.     @Override
  139.     public boolean addEntry(Item item, String version) {
  140.         return addEntryRaw(item) && setRosterVersion(version);
  141.     }

  142.     @Override
  143.     public boolean removeEntry(Jid bareJid, String version) {
  144.         return getBareJidFile(bareJid).delete() && setRosterVersion(version);
  145.     }

  146.     @Override
  147.     public boolean resetEntries(Collection<Item> items, String version) {
  148.         for (File file : fileDir.listFiles(rosterDirFilter)) {
  149.             file.delete();
  150.         }
  151.         for (Item item : items) {
  152.             if (!addEntryRaw(item)) {
  153.                 return false;
  154.             }
  155.         }
  156.         return setRosterVersion(version);
  157.     }

  158.     private Item readEntry(File file) {
  159.         String s = FileUtils.readFile(file);
  160.         if (s == null) {
  161.             return null;
  162.         }

  163.         String parserName;
  164.         Jid user = null;
  165.         String name = null;
  166.         String type = null;
  167.         String status = null;

  168.         List<String> groupNames = new ArrayList<String>();

  169.         try {
  170.             XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
  171.             parser.setInput(new StringReader(s));

  172.             boolean done = false;
  173.             while (!done) {
  174.                 int eventType = parser.next();
  175.                 parserName = parser.getName();
  176.                 if (eventType == XmlPullParser.START_TAG) {
  177.                     if (parserName.equals("item")) {
  178.                         user = null;
  179.                         name = type = status = null;
  180.                     }
  181.                     else if (parserName.equals("user")) {
  182.                         parser.next();
  183.                         user = JidCreate.from(parser.getText());
  184.                     }
  185.                     else if (parserName.equals("name")) {
  186.                         parser.next();
  187.                         name = parser.getText();
  188.                     }
  189.                     else if (parserName.equals("type")) {
  190.                         parser.next();
  191.                         type = parser.getText();
  192.                     }
  193.                     else if (parserName.equals("status")) {
  194.                         parser.next();
  195.                         status = parser.getText();
  196.                     }
  197.                     else if (parserName.equals("group")) {
  198.                         parser.next();
  199.                         parser.next();
  200.                         String group = parser.getText();
  201.                         if (group != null) {
  202.                             groupNames.add(group);
  203.                         }
  204.                         else {
  205.                             log("Invalid group entry in store entry file "
  206.                                     + file);
  207.                         }
  208.                     }
  209.                 }
  210.                 else if (eventType == XmlPullParser.END_TAG) {
  211.                     if (parserName.equals("item")) {
  212.                         done = true;
  213.                     }
  214.                 }
  215.             }
  216.         }
  217.         catch (IOException e) {
  218.             LOGGER.log(Level.SEVERE, "readEntry()", e);
  219.             return null;
  220.         }
  221.         catch (XmlPullParserException e) {
  222.             log("Invalid group entry in store entry file "
  223.                     + file);
  224.             LOGGER.log(Level.SEVERE, "readEntry()", e);
  225.             return null;
  226.         }

  227.         if (user == null) {
  228.             return null;
  229.         }
  230.         RosterPacket.Item item = new RosterPacket.Item(user, name);
  231.         for (String groupName : groupNames) {
  232.             item.addGroupName(groupName);
  233.         }

  234.         if (type != null) {
  235.             try {
  236.                 item.setItemType(RosterPacket.ItemType.valueOf(type));
  237.             }
  238.             catch (IllegalArgumentException e) {
  239.                 log("Invalid type in store entry file " + file);
  240.                 return null;
  241.             }
  242.             if (status != null) {
  243.                 RosterPacket.ItemStatus itemStatus = RosterPacket.ItemStatus
  244.                         .fromString(status);
  245.                 if (itemStatus == null) {
  246.                     log("Invalid status in store entry file " + file);
  247.                     return null;
  248.                 }
  249.                 item.setItemStatus(itemStatus);
  250.             }
  251.         }

  252.         return item;
  253.     }


  254.     private boolean addEntryRaw (Item item) {
  255.         XmlStringBuilder xml = new XmlStringBuilder();
  256.         xml.openElement("item");
  257.         xml.element("user", item.getUser());
  258.         xml.optElement("name", item.getName());
  259.         xml.optElement("type", item.getItemType());
  260.         xml.optElement("status", item.getItemStatus());
  261.         for (String groupName : item.getGroupNames()) {
  262.             xml.openElement("group");
  263.             xml.element("groupName", groupName);
  264.             xml.closeElement("group");
  265.         }
  266.         xml.closeElement("item");

  267.         return FileUtils.writeFile(getBareJidFile(item.getUser()), xml.toString());
  268.     }


  269.     private File getBareJidFile(Jid bareJid) {
  270.         String encodedJid = Base32.encode(bareJid.toString());
  271.         return new File(fileDir, ENTRY_PREFIX + encodedJid);
  272.     }

  273.     private void log(String error) {
  274.         System.err.println(error);
  275.     }
  276. }