private void importSettingsCSV(Reader in, final ImportCallback callback) throws IOException {
    final ICsvBeanReader reader = new CsvBeanReader(in, CsvPreference.STANDARD_PREFERENCE);
    final CellProcessor[] processors =
        new CellProcessor[] {
          null,
          new ConvertNullTo(""),
          null,
          new CellProcessor() {

            @Override
            public Object execute(Object arg, CsvContext ctx) {
              Set<net.solarnetwork.node.Setting.SettingFlag> set = null;
              if (arg != null) {
                int mask = Integer.parseInt(arg.toString());
                set = net.solarnetwork.node.Setting.SettingFlag.setForMask(mask);
              }
              return set;
            }
          },
          new org.supercsv.cellprocessor.ParseDate(SETTING_MODIFIED_DATE_FORMAT)
        };
    reader.getHeader(true);
    final List<Setting> importedSettings = new ArrayList<Setting>();
    transactionTemplate.execute(
        new TransactionCallbackWithoutResult() {

          @Override
          protected void doInTransactionWithoutResult(final TransactionStatus status) {
            Setting s;
            try {
              while ((s = reader.read(Setting.class, CSV_HEADERS, processors)) != null) {
                if (!callback.shouldImportSetting(s)) {
                  continue;
                }
                if (s.getKey() == null) {
                  continue;
                }
                if (s.getValue() == null) {
                  settingDao.deleteSetting(s.getKey(), s.getType());
                } else {
                  settingDao.storeSetting(s);
                  importedSettings.add(s);
                }
              }
            } catch (IOException e) {
              log.error("Unable to import settings: {}", e.getMessage());
              status.setRollbackOnly();
            } finally {
              try {
                reader.close();
              } catch (IOException e) {
                // ingore
              }
              if (status.isRollbackOnly()) {
                importedSettings.clear();
              }
            }
          }
        });

    // now that settings have been imported into DAO layer, we need to apply them to the existing
    // runtime

    // first, determine what factories we have... these have keys like <factoryPID>.FACTORY
    final Map<String, Setting> factorySettings = new HashMap<String, Setting>();
    for (Setting s : importedSettings) {
      if (s.getKey() == null || !s.getKey().endsWith(FACTORY_SETTING_KEY_SUFFIX)) {
        continue;
      }
      String factoryPID =
          s.getKey().substring(0, s.getKey().length() - FACTORY_SETTING_KEY_SUFFIX.length());
      log.debug("Discovered imported factory setting {}", factoryPID);
      factorySettings.put(factoryPID, s);

      // Now create the CA configuration for all defined factories, to handle situation where we
      // don't actually
      // configure any custom settings on the factory. In that case we don't have any settings, but
      // we need
      // to instantiate the factory so we create a default instance.
      try {
        int instanceCount = Integer.valueOf(s.getValue());
        for (int i = 1; i <= instanceCount; i++) {
          String instanceKey = String.valueOf(i);
          Configuration conf = getConfiguration(factoryPID, instanceKey);
          @SuppressWarnings("unchecked")
          Dictionary<String, Object> props = conf.getProperties();
          if (props == null) {
            props = new Hashtable<String, Object>();
            props.put(OSGI_PROPERTY_KEY_FACTORY_INSTANCE_KEY, instanceKey);
            conf.update(props);
          }
        }
      } catch (NumberFormatException e) {
        log.warn(
            "Factory {} setting does not have instance count value: {}",
            factoryPID,
            e.getMessage());
      } catch (InvalidSyntaxException e) {
        log.warn("Factory {} setting has invalid syntax: {}", factoryPID, e.getMessage());
      }
    }

    // now convert imported settings into a SettingsCommand, so values are applied to Configuration
    // Admin
    SettingsCommand cmd = new SettingsCommand();

    for (Setting s : importedSettings) {
      if (s.getKey() == null) {
        continue;
      }

      // skip factory instance definitions
      if (s.getKey().endsWith(FACTORY_SETTING_KEY_SUFFIX)) {
        continue;
      }

      // skip things that don't look like CA settings
      if (!CA_PID_PATTERN.matcher(s.getKey()).matches()
          || s.getType() == null
          || SetupSettings.SETUP_TYPE_KEY.equals(s.getType())
          || s.getType().length() < 1) {
        continue;
      }

      SettingValueBean bean = new SettingValueBean();

      // find out if this is a factory
      for (String factoryPID : factorySettings.keySet()) {
        if (s.getKey().startsWith(factoryPID + ".")
            && s.getKey().length() > (factoryPID.length() + 1)) {
          bean.setProviderKey(factoryPID);
          bean.setInstanceKey(s.getKey().substring(factoryPID.length() + 1));
          break;
        }
      }

      if (bean.getProviderKey() == null) {
        // not a factory setting
        bean.setProviderKey(s.getKey());
      }
      bean.setKey(s.getType());
      bean.setValue(s.getValue());
      bean.setTransient(s.getFlags() != null && s.getFlags().contains(SettingFlag.Volatile));
      cmd.getValues().add(bean);
    }
    if (cmd.getValues().size() > 0) {
      updateSettings(cmd);
    }
  }
  @SuppressWarnings("unchecked")
  @Override
  public void updateSettings(SettingsCommand command) {
    // group all updates by provider+instance, to reduce the number of CA updates
    // when multiple settings are changed
    if (command.getProviderKey() == null) {
      Map<String, SettingsCommand> groups = new LinkedHashMap<String, SettingsCommand>(8);
      Map<String, SettingsCommand> indexedGroups = null;
      for (SettingValueBean bean : command.getValues()) {
        String groupKey =
            bean.getProviderKey() + (bean.getInstanceKey() == null ? "" : bean.getInstanceKey());
        final boolean indexed = INDEXED_PROP_PATTERN.matcher(bean.getKey()).find();
        SettingsCommand cmd = null;
        if (indexed) {
          // indexed property, add in indexed groups
          if (indexedGroups == null) {
            indexedGroups = new LinkedHashMap<String, SettingsCommand>(8);
          }
          cmd = indexedGroups.get(groupKey);
        } else {
          cmd = groups.get(groupKey);
        }

        if (cmd == null) {
          cmd = new SettingsCommand();
          cmd.setProviderKey(bean.getProviderKey());
          cmd.setInstanceKey(bean.getInstanceKey());
          if (indexed) {
            indexedGroups.put(groupKey, cmd);
          } else {
            groups.put(groupKey, cmd);
          }
        }
        cmd.getValues().add(bean);
      }
      for (SettingsCommand cmd : groups.values()) {
        updateSettings(cmd);
      }
      if (indexedGroups != null) {
        for (SettingsCommand cmd : indexedGroups.values()) {
          updateSettings(cmd);
        }
      }
      return;
    }

    try {
      Configuration conf = getConfiguration(command.getProviderKey(), command.getInstanceKey());
      Dictionary<String, Object> props = conf.getProperties();
      if (props == null) {
        props = new Hashtable<String, Object>();
      }
      for (SettingValueBean bean : command.getValues()) {
        String settingKey = command.getProviderKey();
        String instanceKey = command.getInstanceKey();
        if (instanceKey != null) {
          settingKey = getFactoryInstanceSettingKey(settingKey, instanceKey);
        }
        if (bean.isRemove()) {
          props.remove(bean.getKey());
        } else {
          props.put(bean.getKey(), bean.getValue());
        }

        if (!bean.isTransient()) {
          if (bean.isRemove()) {
            settingDao.deleteSetting(settingKey, bean.getKey());
          } else {
            settingDao.storeSetting(settingKey, bean.getKey(), bean.getValue());
          }
        }
      }
      if (conf != null && props != null) {
        if (command.getInstanceKey() != null) {
          props.put(OSGI_PROPERTY_KEY_FACTORY_INSTANCE_KEY, command.getInstanceKey());
        }
        conf.update(props);
      }

    } catch (IOException e) {
      throw new RuntimeException(e);
    } catch (InvalidSyntaxException e) {
      throw new RuntimeException(e);
    }
  }