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