void importData(
        PrismsApplication app,
        PrismsSynchronizer sync,
        String version,
        JsonSerialReader jsr,
        StringBuilder message)
        throws java.io.IOException, SAJParser.ParseException {
      SyncRecord syncRecord =
          new SyncRecord(
              new PrismsCenter("Export"),
              SyncRecord.Type.AUTOMATIC,
              System.currentTimeMillis(),
              false);
      PrismsSynchronizer.SyncTransaction trans =
          sync.transact(
              version, true, syncRecord, false, new prisms.ui.UI.DefaultProgressInformer());

      // Items
      PrismsSynchronizer.PS2ItemReader itemReader = new PrismsSynchronizer.PS2ItemReader(trans);
      if (!jsr.goToProperty("items")) {
        message.append("\n\t\tNo items property in sync data");
        return;
      }
      int items = 0;
      org.json.simple.JSONObject obj;
      jsr.startArray();
      obj = jsr.parseObject();
      while (obj != null) {
        items++;
        try {
          itemReader.read(obj);
        } catch (PrismsRecordException e) {
          log.error("Error parsing item", e);
        }
        obj = jsr.parseObject();
      }
      jsr.endArray(null);
      message.append("\n\t\t").append(items).append(" items");

      PrismsCenter[] centers;
      try {
        centers = sync.getKeeper().getCenters();
      } catch (PrismsRecordException e) {
        message.append("\n\t\t").append("Could not get centers");
        log.error("Could not get centers", e);
        centers = null;
      }
      if (centers != null && trans.getImpl().getCentersProperty() != null) {
        PrismsCenter[] appCenters = centers;
        for (int i = 0; i < appCenters.length; i++)
          if (centers[i].getID() == 0) {
            // Don't include the "Here" center in the UI value
            appCenters = prisms.util.ArrayUtils.remove(appCenters, i);
            break;
          }
        app.setGlobalProperty(
            trans.getImpl().getCentersProperty(),
            appCenters,
            RecordUtils.TRANSACTION_EVENT_PROP,
            new prisms.records.RecordsTransaction());
      }

      // Changes
      PrismsSynchronizer.ChangeReader changeReader =
          new PrismsSynchronizer.ChangeReader(trans, null, itemReader, 0);
      if (!jsr.goToProperty("changes")) {
        message.append("\n\t\tNo changes property in sync data");
        return;
      }
      int changes = 0;
      jsr.startArray();
      obj = jsr.parseObject();
      while (obj != null) {
        changes++;
        changeReader.readChange(obj);
        obj = jsr.parseObject();
      }
      jsr.endArray(null);
      message.append(", ").append(changes).append(" changes");
      changeReader = null;
      itemReader = null;

      // Sync records
      if (centers != null) {
        if (jsr.goToProperty("syncRecords")) {
          int syncRecords = 0;
          jsr.startArray();
          while (jsr.getNextItem(true, false) instanceof JsonSerialReader.ObjectItem) {
            JsonSerialReader.StructState centerState = jsr.save();
            try {
              if (!jsr.goToProperty("id")) {
                message.append("\n\t\tID missing in sync records group");
                break;
              }
              int centerID = jsr.parseInt();
              PrismsCenter center = null;
              for (PrismsCenter c : centers)
                if (c.getID() == centerID) {
                  center = c;
                  break;
                }
              if (center == null) {
                log.warn("No such center with ID " + centerID);
                continue;
              }
              if (!jsr.goToProperty("syncRecords")) {
                message.append("\n\t\tsyncRecords missing in sync records group");
                continue;
              }
              jsr.startArray();
              while (jsr.getNextItem(true, false) instanceof JsonSerialReader.ObjectItem) {
                JsonSerialReader.StructState srState = jsr.save();
                try {
                  if (!jsr.goToProperty("id")) {
                    log.warn("No id property in sync record");
                    continue;
                  }
                  int srID = jsr.parseInt();
                  if (!jsr.goToProperty("parallelID")) {
                    log.warn("No parallelID property in sync record");
                    continue;
                  }
                  int parallelID = jsr.parseInt();
                  if (!jsr.goToProperty("syncType")) {
                    log.warn("No syncType property in sync record");
                    continue;
                  }
                  String typeName = jsr.parseString();
                  SyncRecord.Type type = SyncRecord.Type.byName(typeName);
                  if (type == null) {
                    log.warn("Unrecognized sync type in sync record: " + typeName);
                    continue;
                  }
                  if (!jsr.goToProperty("time")) {
                    log.warn("No time property in sync record");
                    continue;
                  }
                  long time = jsr.parseLong();
                  if (!jsr.goToProperty("isImport")) {
                    log.warn("No isImport property in sync record");
                    continue;
                  }
                  boolean isImport = jsr.parseBoolean();
                  if (!jsr.goToProperty("syncError")) {
                    log.warn("No syncError property in sync record");
                    continue;
                  }
                  Object syncError = jsr.parseNext(false);
                  SyncRecord sr = new SyncRecord(center, type, time, isImport);
                  sr.setID(srID);
                  sr.setParallelID(parallelID);
                  if (syncError instanceof String) sr.setSyncError((String) syncError);
                  else if (syncError == JsonSerialReader.NULL) sr.setSyncError(null);
                  else String.class.cast(syncError);
                  try {
                    sync.getKeeper().putSyncRecord(sr);
                  } catch (PrismsRecordException e) {
                    log.error("Could not persist sync record", e);
                    continue;
                  }
                  syncRecords++;
                  if (!jsr.goToProperty("associated")) {
                    log.warn("No associated property in sync record");
                    continue;
                  }
                  prisms.util.LongList assocIDs = new prisms.util.LongList();
                  prisms.util.BooleanList assocErrors = new prisms.util.BooleanList();
                  jsr.startArray();
                  while (jsr.getNextItem(true, false) instanceof JsonSerialReader.ObjectItem) {
                    if (!jsr.goToProperty("id")) {
                      log.warn("No id for associated change");
                      jsr.endObject(null);
                      continue;
                    }
                    assocIDs.add(jsr.parseLong());
                    if (jsr.goToProperty("error")) assocErrors.add(jsr.parseBoolean());
                    else assocErrors.add(false);
                    jsr.endObject(null);
                  }
                  jsr.endArray(null);
                  ChangeRecord[] assocChanges;
                  try {
                    assocChanges = sync.getKeeper().getItems(assocIDs.toArray());
                  } catch (PrismsRecordException e) {
                    log.error("Could not get associated changes", e);
                    continue;
                  }
                  for (int i = 0; i < assocChanges.length; i++) {
                    if (assocChanges[i] != null)
                      try {
                        sync.getKeeper().associate(assocChanges[i], sr, assocErrors.get(i));
                      } catch (PrismsRecordException e) {
                        log.error("Could not associated change", e);
                      }
                  }
                } finally {
                  jsr.endObject(srState);
                }
              }
              jsr.endArray(null);
            } finally {
              jsr.endObject(centerState);
            }
          }
          jsr.endArray(null);
          message.append(", ").append(syncRecords).append(" sync records");
        }
      }

      // Latest changes
      if (jsr.goToProperty("latestChanges")) {
        JsonSerialReader.StructState rootState = jsr.startArray();
        while (jsr.getNextItem(true, false) instanceof JsonSerialReader.ObjectItem) {
          JsonSerialReader.StructState lcState = jsr.save();
          try {
            if (!jsr.goToProperty("center")) {
              log.warn("No center for latest change entry");
              continue;
            }
            int centerID = jsr.parseInt();
            if (!jsr.goToProperty("subjectCenter")) {
              log.warn("No subjectCenter for latest change entry");
              continue;
            }
            int subjectCenter = jsr.parseInt();
            if (!jsr.goToProperty("latestChange")) {
              log.warn("No latestChange for latest change entry");
              continue;
            }
            long changeTime = jsr.parseLong();
            try {
              sync.getKeeper().setLatestChange(centerID, subjectCenter, changeTime);
            } catch (PrismsRecordException e) {
              log.error("Could not set latest change", e);
            }
          } finally {
            jsr.endObject(lcState);
          }
        }
        jsr.endArray(rootState);
      }
    }
    /**
     * Exports all synchronize-enabled PRISMS data to a file to be imported later
     *
     * @param ui The user interface to communicate with
     * @param global Whether to export ALL data available to synchronization instead of just the
     *     local data and local modifications
     * @return Whether the export succeeded
     */
    public boolean exportData(prisms.ui.UI ui, boolean global) {
      prisms.ui.UI.DefaultProgressInformer pi = new prisms.ui.UI.DefaultProgressInformer();
      pi.setProgressText("Configuring PRISMS applications");
      ui.startTimedTask(pi);
      try {
        for (PrismsApplication app : theApps) {
          if (!app.isConfigured()) {
            pi.setProgressText("Configuring " + app.getName());
            // Configure the application. Can't be done except by the PrismsServer
            prisms.util.PrismsServiceConnector conn =
                new prisms.util.PrismsServiceConnector(
                    theApps[0].getEnvironment().getIDs().getLocalInstance().location,
                    app.getName(),
                    app.getClients()[0].getName(),
                    "System");
            try {
              conn.getConnector()
                  .setTrustManager(
                      new javax.net.ssl.X509TrustManager() {
                        public void checkClientTrusted(
                            java.security.cert.X509Certificate[] chain, String authType)
                            throws java.security.cert.CertificateException {}

                        public void checkServerTrusted(
                            java.security.cert.X509Certificate[] chain, String authType)
                            throws java.security.cert.CertificateException {}

                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                          return null;
                        }
                      });
            } catch (java.security.GeneralSecurityException e) {
              log.error("Could not set trust manager for service connector", e);
            }
            conn.setUserSource(theApps[0].getEnvironment().getUserSource());
            log.debug("Application " + app.getName() + " is not yet configured. Configuring.");
            try {
              conn.init();
              conn.logout(true);
            } catch (java.io.IOException e) {
              if (!app.isConfigured()) {
                ui.error(
                    "Could not configure application "
                        + app.getName()
                        + ". Export data cannot proceed.");
                log.error("Could not configure application " + app.getName(), e);
                return false;
              }
            }
            if (!app.isConfigured()) {
              ui.error(
                  "Could not configure application "
                      + app.getName()
                      + ". Export data cannot proceed.");
              log.error("Could not configure application " + app.getName());
              return false;
            }
          }
        }
        return exportData(ui, pi, global);
      } finally {
        pi.setDone();
      }
    }
    /**
     * Imports data stored in an export file
     *
     * @param apps The applications in the PRISMS environment
     * @return Whether the import succeeded
     */
    public boolean importData(PrismsApplication[] apps) {
      try {
        java.util.HashMap<String, PrismsSynchronizer> syncs;
        java.util.HashMap<String, PrismsApplication> appsByNS;
        syncs = new java.util.HashMap<String, PrismsSynchronizer>();
        appsByNS = new java.util.HashMap<String, PrismsApplication>();
        for (PrismsApplication app : apps) {
          for (prisms.arch.event.PrismsProperty<?> property : app.getGlobalProperties()) {
            if (PrismsSynchronizer.class.isAssignableFrom(property.getType())) {
              PrismsSynchronizer sync = (PrismsSynchronizer) app.getGlobalProperty(property);
              if (sync == null || !(sync.getKeeper() instanceof DBRecordKeeper)) continue;
              syncs.put(((DBRecordKeeper) sync.getKeeper()).getNamespace(), sync);
              appsByNS.put(((DBRecordKeeper) sync.getKeeper()).getNamespace(), app);
            }
          }
        }
        if (!theJsonReader.goToProperty("data")) {
          log.error("No \"data\" property found in exported data");
          return false;
        }

        JsonSerialReader.StructState rootState = theJsonReader.startArray();
        JsonSerialReader.JsonParseItem item;
        StringBuilder message = new StringBuilder();
        message
            .append("Imported PRISMS data from ")
            .append(prisms.util.PrismsUtils.print(theExportDataTime))
            .append(" (");
        prisms.util.PrismsUtils.printTimeLength(
            System.currentTimeMillis() - theExportDataTime, message, false);
        message.append(" old).");
        for (item = theJsonReader.getNextItem(true, false);
            item instanceof JsonSerialReader.ObjectItem;
            item = theJsonReader.getNextItem(true, false)) {
          JsonSerialReader.StructState syncState = theJsonReader.save();
          try {
            /* The current state is now just past the beginning of one synchronizer's exported data */
            if (!"namespace".equals(theJsonReader.getNextProperty())) {
              message.append("\n\tNamespace expected in sync data set");
              continue;
            }
            String namespace = theJsonReader.parseString();
            PrismsSynchronizer sync = syncs.get(namespace);
            if (sync == null) {
              message.append("\n\tNo synchronizer loaded with namespace " + namespace);
              continue;
            }
            message.append("\n\tImporting data for namespace \"").append(namespace).append('"');
            String version;
            if (!"version".equals(theJsonReader.getNextProperty())) version = null;
            else version = theJsonReader.parseString();
            boolean preAI = ((DBRecordKeeper) sync.getKeeper()).hasAbsoluteIntegrity();
            ((DBRecordKeeper) sync.getKeeper()).setAbsoluteIntegrity(true);
            try {
              importData(appsByNS.get(namespace), sync, version, theJsonReader, message);
            } catch (Exception e) {
              message.append("\n\t\tImport failed: ").append(e);
              log.error("Could not import data for synchronizer " + namespace, e);
            } finally {
              ((DBRecordKeeper) sync.getKeeper()).setAbsoluteIntegrity(preAI);
            }
          } finally {
            theJsonReader.endObject(syncState);
          }
        }
        theJsonReader.endArray(rootState);
        int passwords = 0;
        if (theJsonReader.goToProperty("passwords")) {
          rootState = theJsonReader.startArray();
          prisms.util.LongList pwdData = new prisms.util.LongList();
          for (item = theJsonReader.getNextItem(true, false);
              item instanceof JsonSerialReader.ObjectItem;
              item = theJsonReader.getNextItem(true, false)) {
            JsonSerialReader.StructState pwdState = theJsonReader.save();
            try {
              theJsonReader.goToProperty("userName");
              String userName = theJsonReader.parseString();
              User user = apps[0].getEnvironment().getUserSource().getUser(userName);
              if (user == null) continue;
              theJsonReader.goToProperty("passwordData");
              theJsonReader.startArray();
              Number num;
              do {
                num = theJsonReader.parseNumber();
                if (num != null) pwdData.add(num.longValue());
              } while (num != null);
              theJsonReader.endArray(null);
              try {
                apps[0].getEnvironment().getUserSource().setPassword(user, pwdData.toArray(), true);
              } catch (prisms.arch.PrismsException e) {
                log.error("Could not set password for user " + userName, e);
              }
              passwords++;
              pwdData.clear();
            } finally {
              theJsonReader.endObject(pwdState);
            }
          }
          theJsonReader.endArray(rootState);
        }
        message.append("\n\tImported ").append(passwords).append(" passwords");
        log.info(message.toString());
        return true;
      } catch (Exception e) {
        log.error("Could not read or parse exported data", e);
        return false;
      } finally {
        close();
      }
    }
    boolean exportData(prisms.ui.UI ui, prisms.ui.UI.DefaultProgressInformer pi, boolean global) {
      java.io.File exportFile =
          new java.io.File(
              theApps[0].getEnvironment().getLogger().getExposedDir() + ".exportedData.dat");
      if (exportFile.exists() && !prisms.util.FileSegmentizerOutputStream.delete(exportFile)) {
        ui.error(
            "Could not delete data exported on "
                + prisms.util.PrismsUtils.print(exportFile.lastModified()));
        log.error(
            "Could not delete data exported on "
                + prisms.util.PrismsUtils.print(exportFile.lastModified()));
        return false;
      }
      prisms.util.FileSegmentizerOutputStream fileStream = null;
      prisms.util.ExportStream exportStream;
      java.io.OutputStreamWriter streamWriter;
      JsonStreamWriter jsw;
      try {
        fileStream = new prisms.util.FileSegmentizerOutputStream(exportFile);
        exportStream = new prisms.util.ExportStream(fileStream);
        streamWriter = new java.io.OutputStreamWriter(exportStream);
        // streamWriter = new java.io.OutputStreamWriter(fileStream);
        jsw = new JsonStreamWriter(streamWriter);
      } catch (java.io.IOException e) {
        ui.error("Could not write data for export: " + e);
        log.error("Could not write data for export", e);
        if (fileStream != null)
          try {
            fileStream.close();
          } catch (java.io.IOException e2) {
          }
        prisms.util.FileSegmentizerOutputStream.delete(exportFile);
        return false;
      }
      boolean success = false;
      try {
        jsw.startObject();

        jsw.startProperty("exportTime");
        jsw.writeNumber(Long.valueOf(System.currentTimeMillis()));

        jsw.startProperty("instance");
        jsw.writeNumber(Integer.valueOf(theApps[0].getEnvironment().getIDs().getCenterID()));

        jsw.startProperty("hashing");
        Hashing hashing = theApps[0].getEnvironment().getUserSource().getHashing();
        jsw.startObject();
        jsw.startProperty("multiples");
        jsw.startArray();
        for (long h : hashing.getPrimaryMultiples()) jsw.writeNumber(Long.valueOf(h));
        jsw.endArray();
        jsw.startProperty("modulos");
        jsw.startArray();
        for (long h : hashing.getPrimaryModulos()) jsw.writeNumber(Long.valueOf(h));
        jsw.endArray();
        jsw.endObject();

        jsw.startProperty("data");
        jsw.startArray();
        java.util.HashSet<String> namespaces = new java.util.HashSet<String>();
        for (PrismsApplication app : theApps) {
          for (prisms.arch.event.PrismsProperty<?> property : app.getGlobalProperties()) {
            if (PrismsSynchronizer.class.isAssignableFrom(property.getType())) {
              PrismsSynchronizer sync = (PrismsSynchronizer) app.getGlobalProperty(property);
              if (sync == null || !(sync.getKeeper() instanceof DBRecordKeeper)) continue;
              exportData(ui, sync, jsw, namespaces, pi, global);
            }
          }
        }
        jsw.endArray();
        jsw.startProperty("passwords");
        jsw.startArray();
        for (User user : theApps[0].getEnvironment().getUserSource().getActiveUsers()) {
          prisms.arch.ds.UserSource.Password pwd =
              theApps[0].getEnvironment().getUserSource().getPassword(user);
          if (pwd == null) continue;
          jsw.startObject();
          jsw.startProperty("userName");
          jsw.writeString(user.getName());
          jsw.startProperty("passwordData");
          jsw.startArray();
          for (long h : pwd.hash) jsw.writeNumber(Long.valueOf(h));
          jsw.endArray();
          jsw.endObject();
        }
        jsw.endArray();
        jsw.endObject();
        jsw.close();
        streamWriter.close();
        fileStream.close();
        success = true;
        ui.info(
            "Data has been exported. On server restart after rebuild,"
                + " local data will be imported.");
        log.info(
            "Instance "
                + theApps[0].getEnvironment().getIDs().getLocalInstance().location
                + ": Data has been exported to "
                + exportFile.getCanonicalPath());
      } catch (java.io.IOException e) {
        ui.error("Data export failed: " + e);
        log.error("Data export failed", e);
      } catch (prisms.records.PrismsRecordException e) {
        ui.error("Data export failed: " + e);
        log.error("Data export failed", e);
      } finally {
        if (!success) {
          try {
            fileStream.close();
          } catch (java.io.IOException e2) {
          }
          prisms.util.FileSegmentizerOutputStream.delete(exportFile);
        }
      }
      return success;
    }