Java tutorial
/****************************************************************************** * $URL: $ * $Id: $ ****************************************************************************** * * Copyright (c) 2003-2014 The Apereo Foundation * * Licensed under the Educational Community License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://opensource.org/licenses/ecl2 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *****************************************************************************/ package org.sakaiproject.config.impl; import com.ibm.icu.text.SimpleDateFormat; import com.ibm.icu.util.Calendar; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.IllegalClassException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasypt.encryption.pbe.PBEStringEncryptor; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.component.api.ServerConfigurationService.ConfigData; import org.sakaiproject.component.api.ServerConfigurationService.ConfigItem; import org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener; import org.sakaiproject.component.api.ServerConfigurationService.ConfigurationProvider; import org.sakaiproject.component.impl.ConfigItemImpl; import org.sakaiproject.config.api.HibernateConfigItem; import org.sakaiproject.config.api.HibernateConfigItemDao; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * KNL-1063 * Manages the persistence details of a ConfigItem in the form of a HibernateConfigItem. * This service does take into consideration multiple application servers and partitions * the properties appropriately using the serverId. * <p/> * DO NOT USE THIS SERVICE to change properties use ServerConfigurationService. * <p/> * Once SCS has been updated with property changes they will then persisted no need to * do anything else. * * @author Earle Nietzel * Created on Mar 8, 2013 */ public class StoredConfigService implements ConfigurationListener, ConfigurationProvider { // enables persistence of ConfigItems that SCS knows about public static final String SAKAI_CONFIG_STORE_ENABLE = "sakai.config.store.enable"; // registers with SCS as a provider of config public static final String SAKAI_CONFIG_PROVIDE_ENABLE = "sakai.config.provide.enable"; public static final boolean SAKAI_CONFIG_PROVIDE_ENABLE_DEFAULT = true; // enables the database poller to provide config during a running system public static final String SAKAI_CONFIG_POLL_ENABLE = "sakai.config.poll.enable"; // how often to poll for config public static final String SAKAI_CONFIG_POLL_SECONDS = "sakai.config.poll.seconds"; // use unexpanded values hence raw public static final String SAKAI_CONFIG_USE_RAW = "sakai.config.use.raw"; // config that should never be persisted public static final String SAKAI_CONFIG_NEVER_PERSIST = "sakai.config.never.persist"; private final Log log = LogFactory.getLog(StoredConfigService.class); private ScheduledExecutorService scheduler; private ServerConfigurationService serverConfigurationService; private HibernateConfigItemDao dao; private PBEStringEncryptor textEncryptor; private Set<String> neverPersistItems; private String node; public void init() { log.info("init()"); node = serverConfigurationService.getServerId(); if (StringUtils.isBlank(node)) { log.error("init(); node cannot be blank, StoredConfigService is disabled"); return; } // get configured config to skip String[] doNotPersistConfig = serverConfigurationService.getStrings(SAKAI_CONFIG_NEVER_PERSIST); List<String> tmpdoNotPersist; if (doNotPersistConfig == null) { // SCS can return a null here if config is not found tmpdoNotPersist = new ArrayList<String>(); } else { tmpdoNotPersist = Arrays.asList(doNotPersistConfig); } // always add password@org.jasypt.encryption.pbe.PBEStringEncryptor tmpdoNotPersist.add("password@org.jasypt.encryption.pbe.PBEStringEncryptor"); // TODO add more stuff here like serverId, DB password // Setup list of items we should never persist neverPersistItems = Collections.unmodifiableSet(new HashSet<String>(tmpdoNotPersist)); // delete items that should never persisted for (String item : neverPersistItems) { deleteHibernateConfigItem(item); } // enables storing of all config that SCS knows about if (serverConfigurationService.getBoolean(SAKAI_CONFIG_STORE_ENABLE, false)) { learnConfig(serverConfigurationService.getConfigData().getItems()); serverConfigurationService.registerListener(this); } if (serverConfigurationService.getBoolean(SAKAI_CONFIG_POLL_ENABLE, false)) { final int pollDelaySeconds = serverConfigurationService.getInt(SAKAI_CONFIG_POLL_SECONDS, 60); // setup an ExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); // schedule task for every pollDelaySeconds scheduler.scheduleWithFixedDelay(new Runnable() { Date pollDate; @Override public void run() { pollDate = storedConfigPoller(pollDelaySeconds, pollDate); } }, pollDelaySeconds < 120 ? 120 : pollDelaySeconds, // minimally wait 120 seconds for sakai to start pollDelaySeconds, TimeUnit.SECONDS); log.info("init() " + SAKAI_CONFIG_POLL_ENABLE + " is enabled and polling every " + pollDelaySeconds + " seconds"); } } public void destroy() { // terminate the scheduler if (scheduler != null) { scheduler.shutdown(); } } private Date storedConfigPoller(int delay, Date then) { Calendar calendar = Calendar.getInstance(); Date now = calendar.getTime(); if (then == null) { // on start set then calendar.add(Calendar.SECOND, -(delay)); then = calendar.getTime(); } List<HibernateConfigItem> polled = findPollOn(then, now); int registered = 0; for (HibernateConfigItem item : polled) { if (item.isRegistered()) { // registering items that are not marked as registered leads to bad things in SCS ConfigItem cItem = serverConfigurationService.getConfigItem(item.getName()); if (!item.similar(cItem)) { // only register items that are not similar, reduces history on items in SCS serverConfigurationService.registerConfigItem(createConfigItem(item)); registered++; } } else { log.warn("storedConfigPoller() item " + item.getName() + " is not registered skipping"); } } if (log.isDebugEnabled()) { log.debug("storedConfigPoller() Polling found " + polled.size() + " config item(s) (from " + new SimpleDateFormat("HH:mm:ss").format(then) + " to " + new SimpleDateFormat("HH:mm:ss").format(now) + "), " + registered + " item(s) registered"); } return now; } /** * Creates a HibernateConfigItem for every item in the list and persists it for this node. * * @param items List of ConfigItem */ private void learnConfig(List<ConfigItem> items) { if (items == null) { return; } int total = items.size(); int updated = 0; int created = 0; for (ConfigItem item : items) { if (item == null) { continue; // skip this item } HibernateConfigItem hItem = null; int rows = countByName(item.getName()); if (rows == 0) { // new item, create it hItem = createHibernateConfigItem(item); created++; } else { // sync config with files when store enabled and not provide enabled if (!serverConfigurationService.getBoolean(SAKAI_CONFIG_PROVIDE_ENABLE, SAKAI_CONFIG_PROVIDE_ENABLE_DEFAULT)) { hItem = findByName(item.getName()); hItem = updateHibernateConfigItem(hItem, item); if (hItem != null) { // if hItem is not null it was updated updated++; } } } saveOrUpdate(hItem); } log.info("initItems() processed " + total + " config items, updated " + updated + " created " + created); } /** * Persists or updates (if it already exists) a HibernateConfigItem to the database for this node * * @param hItem a HibernateConfigItem */ public void saveOrUpdate(HibernateConfigItem hItem) { if (hItem == null) { return; } String name = hItem.getName(); String type = hItem.getType(); if (name == null || name.isEmpty()) { log.warn("saveOrUpdate() item name is missing"); return; } else if (!(ServerConfigurationService.TYPE_STRING.equals(type) || ServerConfigurationService.TYPE_INT.equals(type) || ServerConfigurationService.TYPE_BOOLEAN.equals(type) || ServerConfigurationService.TYPE_ARRAY.equals(type))) { log.warn("saveOrUpdate() item type is incorrect"); return; } dao.saveOrUpdate(hItem); } /** * Get all registered HibernateConfigItem as ConfigItems for this node * * @return a List of ConfigItem */ public List<ConfigItem> getConfigItems() { List<ConfigItem> configItems = new ArrayList<ConfigItem>(); List<HibernateConfigItem> regItems = findRegistered(); for (HibernateConfigItem hItem : regItems) { ConfigItem item = createConfigItem(hItem); if (item != null) { configItems.add(item); if (log.isDebugEnabled()) { log.debug("getConfigItems() " + item.toString()); } } } return configItems; } /** * Creates an equivalent ConfigItem from a HibernateConfigItem * * @param hItem a HibernateConfigItem * @return a ConfigItem * @throws IllegalClassException this can occur during deserialization when creating a ConfigItem */ public ConfigItem createConfigItem(HibernateConfigItem hItem) throws IllegalClassException { if (hItem == null) { return null; } String value; if (serverConfigurationService.getBoolean(SAKAI_CONFIG_USE_RAW, false) && StringUtils.isNotBlank(hItem.getRawValue())) { value = hItem.getRawValue(); } else { value = hItem.getValue(); } // create new ConfigItem ConfigItem item = new ConfigItemImpl(hItem.getName(), deSerializeValue(value, hItem.getType(), hItem.isSecured()), hItem.getType(), hItem.getDescription(), this.getClass().getName(), deSerializeValue(hItem.getDefaultValue(), hItem.getType(), hItem.isSecured()), 0, // requested 0, // changed null, // history hItem.isRegistered(), hItem.isDefaulted(), hItem.isSecured(), hItem.isDynamic()); if (log.isDebugEnabled()) { log.debug("createConfigItem() " + item.toString()); } return item; } /** * Creates an equivalent HibernateConfigItem from a ConfigItem * * @param item a ConfigItem * @return a HibernateConfigItem * @throws IllegalClassException thrown when the item.getValue doesn't return the right type */ public HibernateConfigItem createHibernateConfigItem(ConfigItem item) throws IllegalClassException { if (item == null || neverPersistItems.contains(item.getName())) { return null; } if (log.isDebugEnabled()) { log.debug("createHibernateConfigItem() New ConfigItem = " + item.toString()); } String serialValue; String serialDefaultValue; String serialRawValue; try { serialValue = serializeValue(item.getValue(), item.getType(), item.isSecured()); serialDefaultValue = serializeValue(item.getDefaultValue(), item.getType(), item.isSecured()); serialRawValue = serializeValue(getRawProperty(item.getName()), ServerConfigurationService.TYPE_STRING, item.isSecured()); } catch (IllegalClassException ice) { log.error("createHibernateConfigItem() IllegalClassException " + ice.getMessage() + " skip ConfigItem " + item.toString(), ice); return null; } HibernateConfigItem hItem = new HibernateConfigItem(node, item.getName(), serialValue, serialRawValue, item.getType(), item.getDescription(), item.getSource(), serialDefaultValue, item.isRegistered(), item.isDefaulted(), item.isSecured(), item.isDynamic()); if (log.isDebugEnabled()) { log.debug("createHibernateConfigItem() Created HibernateConfigItem = " + hItem.toString()); } return hItem; } /** * Updates a HibernateConfigItem with a ConfigItem's data; the name, node * * @param hItem a HibernateConfigItem * @param item a ConfigItem * @return a HibernateConfigItem if it was updated or null if it was not updated * @throws IllegalClassException thrown when the item.getValue doesn't return the right type */ public HibernateConfigItem updateHibernateConfigItem(HibernateConfigItem hItem, ConfigItem item) throws IllegalClassException { if (hItem == null || item == null) { return null; } HibernateConfigItem updatedItem = null; // check if updating is needed, update it if (!hItem.similar(item)) { // if they are not similar update it if (log.isDebugEnabled()) { log.debug("updateHibernateConfigItem() Before " + hItem.toString()); } Object value = deSerializeValue(hItem.getValue(), hItem.getType(), hItem.isSecured()); Object defaultValue = deSerializeValue(hItem.getDefaultValue(), hItem.getType(), hItem.isSecured()); try { if (value == null) { if (item.getValue() != null) { // different update hItem.setValue(serializeValue(item.getValue(), item.getType(), item.isSecured())); } } else if (!value.equals(item.getValue())) { // different update hItem.setValue(serializeValue(item.getValue(), item.getType(), item.isSecured())); } if (defaultValue == null) { if (item.getDefaultValue() != null) { // different update hItem.setDefaultValue( serializeValue(item.getDefaultValue(), item.getType(), item.isSecured())); } } else if (!defaultValue.equals(item.getDefaultValue())) { // different update hItem.setDefaultValue(serializeValue(item.getDefaultValue(), item.getType(), item.isSecured())); } } catch (IllegalClassException ice) { log.error("updateHibernateConfigItem() IllegalClassException " + ice.getMessage() + " skip ConfigItem " + item.toString(), ice); return null; } hItem.setType(item.getType()); hItem.setDefaulted(item.isDefaulted()); hItem.setSecured(item.isSecured()); hItem.setRegistered(item.isRegistered()); hItem.setSource(item.getSource()); hItem.setDescription(item.getDescription()); hItem.setDynamic(item.isDynamic()); hItem.setModified(Calendar.getInstance().getTime()); if (log.isDebugEnabled()) { log.debug("updateHibernateConfigItem() After " + hItem.toString()); } updatedItem = hItem; } // check if raw value needs updating // raw values are always strings String rawValue = (String) deSerializeValue(hItem.getRawValue(), ServerConfigurationService.TYPE_STRING, hItem.isSecured()); if (!StringUtils.equals(rawValue, getRawProperty(hItem.getName()))) { // different update hItem.setRawValue(serializeValue(getRawProperty(hItem.getName()), ServerConfigurationService.TYPE_STRING, item.isSecured())); updatedItem = hItem; } // only return a hItem if it was updated return updatedItem; } /** * Delete a HibernateConfigItem * * @param name The HibernateConfigItem that should be deleted */ public void deleteHibernateConfigItem(String name) { if (StringUtils.isBlank(name)) { return; } HibernateConfigItem hItem = findByName(name); if (hItem != null) { log.info("deleteHibernateConfigItem() delete HibernateConfigItem = " + hItem); dao.delete(hItem); } } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener#changed(org.sakaiproject.component.api.ServerConfigurationService.ConfigItem, org.sakaiproject.component.api.ServerConfigurationService.ConfigItem) */ @Override public void changed(ConfigItem item, ConfigItem previous) { // a ConfigItem has changed if (item == null) { return; } HibernateConfigItem hItem = findByName(item.getName()); if (hItem == null) { // new hItem hItem = createHibernateConfigItem(item); } else { // existing hItem, update it hItem = updateHibernateConfigItem(hItem, item); } saveOrUpdate(hItem); } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener#changing(org.sakaiproject.component.api.ServerConfigurationService.ConfigItem, org.sakaiproject.component.api.ServerConfigurationService.ConfigItem) */ @Override public ConfigItem changing(ConfigItem currentConfigItem, ConfigItem newConfigItem) { // no need to do anything before item has changed return null; } @Override public List<ConfigItem> registerConfigItems(ConfigData configData) { if (serverConfigurationService.getBoolean(SAKAI_CONFIG_PROVIDE_ENABLE, SAKAI_CONFIG_PROVIDE_ENABLE_DEFAULT)) { return getConfigItems(); } return Collections.emptyList(); } /** * Find a HibernateConfigItem with a matching name for this node * * @param name the name of the item to find * @return a HibernateConfigItem matching the name for this node or null if none found */ public HibernateConfigItem findByName(String name) { HibernateConfigItem item = null; List<HibernateConfigItem> list = dao.findAllByCriteriaByNode(node, name, null, null, null, null); if (list.size() == 1) { item = list.get(0); } return item; } /** * Number of properties with a matching name on this node * * @param name the name of the item to count * @return a positive integer or -1 if name or node is null */ public int countByName(String name) { return dao.countByNodeAndName(node, name); } /** * Number of all properties on this node * * @return a positive integer or -1 if node is null */ public int countAll() { return dao.countByNode(node); } /** * Find all HibernateConfigItem(s) for this node * * @return a List of HibernateConfigItem(s) */ public List<HibernateConfigItem> findAll() { return dao.findAllByCriteriaByNode(node, null, null, null, null, null); } /** * Find all Secured HibernateConfigItem(s) for this node * * @return a List of HibernateConfigItem(s) */ public List<HibernateConfigItem> findSecured() { return dao.findAllByCriteriaByNode(node, null, null, null, null, true); } /** * Find all Registered HibernateConfigItem(s) for this node * * @return a List of HibernateConfigItem(s) */ public List<HibernateConfigItem> findRegistered() { return dao.findAllByCriteriaByNode(node, null, null, true, null, null); } /** * Find all Dynamic HibernateConfigItem(s) for this node * * @return a List of HibernateConfigItem(s) */ public List<HibernateConfigItem> findDynamic() { return dao.findAllByCriteriaByNode(node, null, null, null, true, null); } /** * Find all Defaulted HibernateConfigItem(s) for this node * * @return a List of HibernateConfigItem(s) */ public List<HibernateConfigItem> findDefaulted() { return dao.findAllByCriteriaByNode(node, null, true, null, null, null); } /** * Find all HibernateConfigItem(s) that have a pollOn during a specified time * If a date is null then that date is left open then time is unbounded in that direction * If both dates are null then every config item that has a pollOn date is returned * * @param after select items after this timestamp * @param before select items before this timestamp * @return a List of HibernateConfigItem(s) */ public List<HibernateConfigItem> findPollOn(Date after, Date before) { return dao.findPollOnByNode(node, after, before); } private String getRawProperty(String name) { if (StringUtils.isBlank(name)) { return null; } String value = null; ConfigItem ci = serverConfigurationService.getConfigItem(name); // null if it does not exist if (ci != null) { String rawValue = serverConfigurationService.getRawProperty(name); value = StringUtils.trimToNull(rawValue); } return value; } private Object deSerializeValue(String value, String type, boolean secured) throws IllegalClassException { if (value == null || type == null) { return null; } String string; if (secured) { // sanity check should be Base64 encoded if (Base64.isBase64(value)) { string = textEncryptor.decrypt(value); } else { log.warn( "deSerializeValue() Invalid value found attempting to decrypt a secured property, check your secured properties"); string = value; } } else { string = value; } Object obj; if (ServerConfigurationService.TYPE_STRING.equals(type)) { obj = string; } else if (ServerConfigurationService.TYPE_INT.equals(type)) { obj = Integer.valueOf(string); } else if (ServerConfigurationService.TYPE_BOOLEAN.equals(type)) { obj = Boolean.valueOf(string); } else if (ServerConfigurationService.TYPE_ARRAY.equals(type)) { obj = string.split(HibernateConfigItem.ARRAY_SEPARATOR); } else { throw new IllegalClassException("deSerializeValue() invalid TYPE, while deserializing"); } return obj; } private String serializeValue(Object obj, String type, boolean secured) throws IllegalClassException { if (obj == null || type == null) { return null; } String string; if (ServerConfigurationService.TYPE_STRING.equals(type)) { if (obj instanceof String) { string = String.valueOf(obj); } else { throw new IllegalClassException(String.class, obj); } } else if (ServerConfigurationService.TYPE_INT.equals(type)) { if (obj instanceof Integer) { string = Integer.toString((Integer) obj); } else { throw new IllegalClassException(Integer.class, obj); } } else if (ServerConfigurationService.TYPE_BOOLEAN.equals(type)) { if (obj instanceof Boolean) { string = Boolean.toString((Boolean) obj); } else { throw new IllegalClassException(Boolean.class, obj); } } else if (ServerConfigurationService.TYPE_ARRAY.equals(type)) { if (obj instanceof String[]) { string = StringUtils.join((String[]) obj, HibernateConfigItem.ARRAY_SEPARATOR); } else { throw new IllegalClassException("serializeValue() expected an array of type String[]"); } } else { throw new IllegalClassException("serializeValue() invalid TYPE, while serializing"); } if (secured) { string = textEncryptor.encrypt(string); } return string; } public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) { this.serverConfigurationService = serverConfigurationService; } public void setDao(HibernateConfigItemDao dao) { this.dao = dao; } public void setTextEncryptor(PBEStringEncryptor textEncryptor) { this.textEncryptor = textEncryptor; } }