001package de.deepamehta.plugins.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.getId()); 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 // --- 111 112 @GET 113 @Override 114 public ConfigDefinitions getConfigDefinitions() { 115 return new ConfigDefinitions(registry); 116 } 117 118 119 120 // ******************************** 121 // *** Listener Implementations *** 122 // ******************************** 123 124 125 126 @Override 127 public void postCreateTopic(Topic topic) { 128 for (ConfigDefinition configDef : getApplicableConfigDefinitions(topic)) { 129 createConfigTopic(configDef, topic.getId()); 130 } 131 } 132 133 134 135 // ------------------------------------------------------------------------------------------------- Private Methods 136 137 private RelatedTopic _getConfigTopic(String configTypeUri, long topicId) { 138 return dms.getAccessControl().getConfigTopic(configTypeUri, topicId); 139 } 140 141 private RelatedTopic createConfigTopic(final ConfigDefinition configDef, final long topicId) { 142 final String configTypeUri = configDef.getConfigTypeUri(); 143 try { 144 logger.info("### Creating config topic of type \"" + configTypeUri + "\" for topic " + topicId); 145 // We suppress standard workspace assignment here as a config topic requires a special assignment. 146 // See assignConfigTopicToWorkspace() below. 147 return dms.getAccessControl().runWithoutWorkspaceAssignment(new Callable<RelatedTopic>() { 148 @Override 149 public RelatedTopic call() { 150 Topic configTopic = dms.createTopic(configDef.getDefaultConfigTopic()); 151 dms.createAssociation(new AssociationModel(ASSOC_TYPE_CONFIGURATION, 152 new TopicRoleModel(topicId, ROLE_TYPE_CONFIGURABLE), 153 new TopicRoleModel(configTopic.getId(), ROLE_TYPE_DEFAULT))); 154 assignConfigTopicToWorkspace(configTopic, configDef.getConfigModificationRole()); 155 // ### TODO: extend Core API to avoid re-retrieval 156 return _getConfigTopic(configTypeUri, topicId); 157 } 158 }); 159 } catch (Exception e) { 160 throw new RuntimeException("Creating config topic of type \"" + configTypeUri + "\" for topic " + 161 topicId + " failed", e); 162 } 163 } 164 165 private void assignConfigTopicToWorkspace(Topic configTopic, ConfigModificationRole role) { 166 long workspaceId; 167 AccessControl ac = dms.getAccessControl(); 168 switch (role) { 169 case ADMIN: 170 workspaceId = ac.getSystemWorkspaceId(); 171 break; 172 default: 173 throw new RuntimeException("Modification role \"" + role + "\" not yet implemented"); 174 } 175 ac.assignToWorkspace(configTopic, workspaceId); 176 } 177 178 // --- 179 180 /** 181 * Returns all configuration definitions applicable to a given topic. 182 * 183 * @return a list of configuration definitions, possibly empty. 184 */ 185 private List<ConfigDefinition> getApplicableConfigDefinitions(Topic topic) { 186 List<ConfigDefinition> configDefs1 = lookupConfigDefinitions(ConfigTarget.SINGLETON.hashKey(topic)); 187 List<ConfigDefinition> configDefs2 = lookupConfigDefinitions(ConfigTarget.TYPE_INSTANCES.hashKey(topic)); 188 if (configDefs1 != null && configDefs2 != null) { 189 List<ConfigDefinition> configDefs = new ArrayList(); 190 configDefs.addAll(configDefs1); 191 configDefs.addAll(configDefs2); 192 return configDefs; 193 } 194 return configDefs1 != null ? configDefs1 : configDefs2 != null ? configDefs2 : new ArrayList(); 195 } 196 197 /** 198 * Returns the configuration definition for the given config type that is applicable to the given topic. 199 * 200 * @throws RuntimeException if no such configuration definition is registered. 201 */ 202 private ConfigDefinition getApplicableConfigDefinition(Topic topic, String configTypeUri) { 203 List<ConfigDefinition> configDefs = getApplicableConfigDefinitions(topic); 204 if (configDefs.size() == 0) { 205 throw new RuntimeException("None of the registered configuration definitions are applicable to " + 206 info(topic)); 207 } 208 ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri); 209 if (configDef == null) { 210 throw new RuntimeException("For " + info(topic) + " no configuration definition for type \"" + 211 configTypeUri + "\" registered"); 212 } 213 return configDef; 214 } 215 216 // --- 217 218 private boolean isRegistered(ConfigDefinition configDef) { 219 for (List<ConfigDefinition> configDefs : registry.values()) { 220 if (configDefs.contains(configDef)) { 221 return true; 222 } 223 } 224 return false; 225 } 226 227 private ConfigDefinition findByConfigTypeUri(List<ConfigDefinition> configDefs, String configTypeUri) { 228 for (ConfigDefinition configDef : configDefs) { 229 if (configDef.getConfigTypeUri().equals(configTypeUri)) { 230 return configDef; 231 } 232 } 233 return null; 234 } 235 236 private List<ConfigDefinition> lookupConfigDefinitions(String hashKey) { 237 return registry.get(hashKey); 238 } 239 240 // --- 241 242 private String info(Topic topic) { 243 return "topic " + topic.getId() + " (value=\"" + topic.getSimpleValue() + "\", typeUri=\"" + 244 topic.getTypeUri() + "\", uri=\"" + topic.getUri() + "\")"; 245 } 246}