001package de.deepamehta.config; 002 003import de.deepamehta.core.RelatedTopic; 004import de.deepamehta.core.Topic; 005import de.deepamehta.core.model.AssociationModel; 006import de.deepamehta.core.model.SimpleValue; 007import de.deepamehta.core.model.TopicRoleModel; 008import de.deepamehta.core.osgi.PluginActivator; 009import de.deepamehta.core.service.Transactional; 010import de.deepamehta.core.service.accesscontrol.AccessControl; 011import de.deepamehta.core.service.event.PostCreateTopicListener; 012 013import javax.ws.rs.GET; 014import javax.ws.rs.Path; 015import javax.ws.rs.PathParam; 016import javax.ws.rs.Produces; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.concurrent.Callable; 023import java.util.logging.Logger; 024 025 026 027@Path("/config") 028@Produces("application/json") 029public class ConfigPlugin extends PluginActivator implements ConfigService, PostCreateTopicListener { 030 031 // ------------------------------------------------------------------------------------------------------- Constants 032 033 private static String ASSOC_TYPE_CONFIGURATION = "dm4.config.configuration"; 034 private static String ROLE_TYPE_CONFIGURABLE = "dm4.config.configurable"; 035 private static String ROLE_TYPE_DEFAULT = "dm4.core.default"; 036 037 // ---------------------------------------------------------------------------------------------- Instance Variables 038 039 /** 040 * Key: the "configurable URI" as a config target's hash key, that is either "topic_uri:{uri}" or "type_uri:{uri}". 041 */ 042 private Map<String, List<ConfigDefinition>> registry = new HashMap(); 043 044 private Logger logger = Logger.getLogger(getClass().getName()); 045 046 // -------------------------------------------------------------------------------------------------- Public Methods 047 048 049 050 // ************************************ 051 // *** ConfigService Implementation *** 052 // ************************************ 053 054 055 056 @GET 057 @Path("/{config_type_uri}/topic/{topic_id}") 058 @Override 059 public RelatedTopic getConfigTopic(@PathParam("config_type_uri") String configTypeUri, 060 @PathParam("topic_id") long topicId) { 061 return _getConfigTopic(configTypeUri, topicId); 062 } 063 064 @Override 065 public void createConfigTopic(String configTypeUri, Topic topic) { 066 createConfigTopic(getApplicableConfigDefinition(topic, configTypeUri), topic); 067 } 068 069 // --- 070 071 @Override 072 public void registerConfigDefinition(ConfigDefinition configDef) { 073 try { 074 if (isRegistered(configDef)) { 075 throw new RuntimeException("A definition for configuration type \"" + configDef.getConfigTypeUri() + 076 "\" is already registered"); 077 } 078 // 079 String hashKey = configDef.getHashKey(); 080 List<ConfigDefinition> configDefs = lookupConfigDefinitions(hashKey); 081 if (configDefs == null) { 082 configDefs = new ArrayList(); 083 registry.put(hashKey, configDefs); 084 } 085 configDefs.add(configDef); 086 } catch (Exception e) { 087 throw new RuntimeException("Registering a configuration definition failed", e); 088 } 089 } 090 091 @Override 092 public void unregisterConfigDefinition(String configTypeUri) { 093 try { 094 for (List<ConfigDefinition> configDefs : registry.values()) { 095 ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri); 096 if (configDef != null) { 097 if (!configDefs.remove(configDef)) { 098 throw new RuntimeException("Configuration definition could not be removed from registry"); 099 } 100 return; 101 } 102 } 103 throw new RuntimeException("No such configuration definition registered"); 104 } catch (Exception e) { 105 throw new RuntimeException("Unregistering definition for configuration type \"" + configTypeUri + 106 "\" failed", e); 107 } 108 } 109 110 // --- not part of OSGi service --- 111 112 @GET 113 public ConfigDefinitions getConfigDefinitions() { 114 return new ConfigDefinitions(registry); 115 } 116 117 118 119 // ******************************** 120 // *** Listener Implementations *** 121 // ******************************** 122 123 124 125 @Override 126 public void postCreateTopic(Topic topic) { 127 for (ConfigDefinition configDef : getApplicableConfigDefinitions(topic)) { 128 createConfigTopic(configDef, topic); 129 } 130 } 131 132 133 134 // ------------------------------------------------------------------------------------------------- Private Methods 135 136 private RelatedTopic _getConfigTopic(String configTypeUri, long topicId) { 137 return dm4.getAccessControl().getConfigTopic(configTypeUri, topicId); 138 } 139 140 private RelatedTopic createConfigTopic(final ConfigDefinition configDef, final Topic topic) { 141 final String configTypeUri = configDef.getConfigTypeUri(); 142 try { 143 logger.info("### Creating config topic of type \"" + configTypeUri + "\" for topic " + topic.getId()); 144 // We suppress standard workspace assignment here as a config topic requires a special assignment. 145 // See assignConfigTopicToWorkspace() below. 146 return dm4.getAccessControl().runWithoutWorkspaceAssignment(new Callable<RelatedTopic>() { 147 @Override 148 public RelatedTopic call() { 149 Topic configTopic = dm4.createTopic(configDef.getConfigValue(topic)); 150 dm4.createAssociation(mf.newAssociationModel(ASSOC_TYPE_CONFIGURATION, 151 mf.newTopicRoleModel(topic.getId(), ROLE_TYPE_CONFIGURABLE), 152 mf.newTopicRoleModel(configTopic.getId(), ROLE_TYPE_DEFAULT))); 153 assignConfigTopicToWorkspace(configTopic, configDef.getConfigModificationRole()); 154 // ### TODO: extend Core API to avoid re-retrieval 155 return _getConfigTopic(configTypeUri, topic.getId()); 156 } 157 }); 158 } catch (Exception e) { 159 throw new RuntimeException("Creating config topic of type \"" + configTypeUri + "\" for topic " + 160 topic.getId() + " failed", e); 161 } 162 } 163 164 private void assignConfigTopicToWorkspace(Topic configTopic, ConfigModificationRole role) { 165 long workspaceId; 166 AccessControl ac = dm4.getAccessControl(); 167 switch (role) { 168 case ADMIN: 169 workspaceId = ac.getAdministrationWorkspaceId(); 170 break; 171 case SYSTEM: 172 workspaceId = ac.getSystemWorkspaceId(); 173 break; 174 default: 175 throw new RuntimeException("Modification role \"" + role + "\" not yet implemented"); 176 } 177 ac.assignToWorkspace(configTopic, workspaceId); 178 } 179 180 // --- 181 182 /** 183 * Returns all configuration definitions applicable to a given topic. 184 * 185 * @return a list of configuration definitions, possibly empty. 186 */ 187 private List<ConfigDefinition> getApplicableConfigDefinitions(Topic topic) { 188 List<ConfigDefinition> configDefs1 = lookupConfigDefinitions(ConfigTarget.SINGLETON.hashKey(topic)); 189 List<ConfigDefinition> configDefs2 = lookupConfigDefinitions(ConfigTarget.TYPE_INSTANCES.hashKey(topic)); 190 if (configDefs1 != null && configDefs2 != null) { 191 List<ConfigDefinition> configDefs = new ArrayList(); 192 configDefs.addAll(configDefs1); 193 configDefs.addAll(configDefs2); 194 return configDefs; 195 } 196 return configDefs1 != null ? configDefs1 : configDefs2 != null ? configDefs2 : new ArrayList(); 197 } 198 199 /** 200 * Returns the configuration definition for the given config type that is applicable to the given topic. 201 * 202 * @throws RuntimeException if no such configuration definition is registered. 203 */ 204 private ConfigDefinition getApplicableConfigDefinition(Topic topic, String configTypeUri) { 205 List<ConfigDefinition> configDefs = getApplicableConfigDefinitions(topic); 206 if (configDefs.size() == 0) { 207 throw new RuntimeException("None of the registered configuration definitions are applicable to " + 208 info(topic)); 209 } 210 ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri); 211 if (configDef == null) { 212 throw new RuntimeException("For " + info(topic) + " no configuration definition for type \"" + 213 configTypeUri + "\" registered"); 214 } 215 return configDef; 216 } 217 218 // --- 219 220 private boolean isRegistered(ConfigDefinition configDef) { 221 for (List<ConfigDefinition> configDefs : registry.values()) { 222 if (configDefs.contains(configDef)) { 223 return true; 224 } 225 } 226 return false; 227 } 228 229 private ConfigDefinition findByConfigTypeUri(List<ConfigDefinition> configDefs, String configTypeUri) { 230 for (ConfigDefinition configDef : configDefs) { 231 if (configDef.getConfigTypeUri().equals(configTypeUri)) { 232 return configDef; 233 } 234 } 235 return null; 236 } 237 238 private List<ConfigDefinition> lookupConfigDefinitions(String hashKey) { 239 return registry.get(hashKey); 240 } 241 242 // --- 243 244 private String info(Topic topic) { 245 return "topic " + topic.getId() + " (value=\"" + topic.getSimpleValue() + "\", typeUri=\"" + 246 topic.getTypeUri() + "\", uri=\"" + topic.getUri() + "\")"; 247 } 248}