001package systems.dmx.config; 002 003import systems.dmx.core.RelatedTopic; 004import systems.dmx.core.Topic; 005import systems.dmx.core.model.AssociationModel; 006import systems.dmx.core.model.SimpleValue; 007import systems.dmx.core.model.TopicRoleModel; 008import systems.dmx.core.osgi.PluginActivator; 009import systems.dmx.core.service.Transactional; 010import systems.dmx.core.service.accesscontrol.AccessControl; 011import systems.dmx.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 = "dmx.config.configuration"; 040 private static String ROLE_TYPE_CONFIGURABLE = "dmx.config.configurable"; 041 private static String ROLE_TYPE_DEFAULT = "dmx.core.default"; 042 043 // ---------------------------------------------------------------------------------------------- Instance Variables 044 045 /** 046 * Key: the "configurable URI" as a config target's hash key, that is either "topicUri:{uri}" or "typeUri:{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 config 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 config 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("Config definition could not be removed from registry"); 108 } 109 return; 110 } 111 } 112 throw new RuntimeException("No such config definition registered"); 113 } catch (Exception e) { 114 throw new RuntimeException("Unregistering definition for config type \"" + configTypeUri + "\" failed", e); 115 } 116 } 117 118 // --- not part of OSGi service --- 119 120 @GET 121 public ConfigDefinitions getConfigDefinitions() { 122 try { 123 JSONObject json = new JSONObject(); 124 AccessControl ac = dmx.getAccessControl(); 125 for (String configurableUri: registry.keySet()) { 126 JSONArray array = new JSONArray(); 127 for (ConfigDefinition configDef : lookupConfigDefinitions(configurableUri)) { 128 String username = ac.getUsername(request); 129 long workspaceId = workspaceId(configDef.getConfigModificationRole()); 130 if (ac.hasReadPermission(username, workspaceId)) { 131 array.put(configDef.getConfigTypeUri()); 132 } 133 } 134 json.put(configurableUri, array); 135 } 136 return new ConfigDefinitions(json); 137 } catch (Exception e) { 138 throw new RuntimeException("Retrieving the registered config definitions failed", e); 139 } 140 } 141 142 143 144 // ******************************** 145 // *** Listener Implementations *** 146 // ******************************** 147 148 149 150 @Override 151 public void postCreateTopic(Topic topic) { 152 for (ConfigDefinition configDef : getApplicableConfigDefinitions(topic)) { 153 _createConfigTopic(configDef, topic); 154 } 155 } 156 157 158 159 // ------------------------------------------------------------------------------------------------- Private Methods 160 161 private RelatedTopic _getConfigTopic(String configTypeUri, long topicId) { 162 return dmx.getAccessControl().getConfigTopic(configTypeUri, topicId); 163 } 164 165 private RelatedTopic _createConfigTopic(final ConfigDefinition configDef, final Topic topic) { 166 final String configTypeUri = configDef.getConfigTypeUri(); 167 try { 168 logger.info("### Creating config topic of type \"" + configTypeUri + "\" for topic " + topic.getId()); 169 // suppress standard workspace assignment as a config topic requires a special assignment 170 final AccessControl ac = dmx.getAccessControl(); 171 return ac.runWithoutWorkspaceAssignment(new Callable<RelatedTopic>() { 172 @Override 173 public RelatedTopic call() { 174 Topic configTopic = dmx.createTopic(configDef.getConfigValue(topic)); 175 dmx.createAssociation(mf.newAssociationModel(ASSOC_TYPE_CONFIGURATION, 176 mf.newTopicRoleModel(topic.getId(), ROLE_TYPE_CONFIGURABLE), 177 mf.newTopicRoleModel(configTopic.getId(), ROLE_TYPE_DEFAULT))); 178 ac.assignToWorkspace(configTopic, workspaceId(configDef.getConfigModificationRole())); 179 // ### TODO: extend Core API to avoid re-retrieval 180 return _getConfigTopic(configTypeUri, topic.getId()); 181 } 182 }); 183 } catch (Exception e) { 184 throw new RuntimeException("Creating config topic of type \"" + configTypeUri + "\" for topic " + 185 topic.getId() + " failed", e); 186 } 187 } 188 189 private long workspaceId(ConfigModificationRole role) { 190 AccessControl ac = dmx.getAccessControl(); 191 switch (role) { 192 case ADMIN: 193 return ac.getAdministrationWorkspaceId(); 194 case SYSTEM: 195 return ac.getSystemWorkspaceId(); 196 default: 197 throw new RuntimeException("Modification role \"" + role + "\" not yet implemented"); 198 } 199 } 200 201 // --- 202 203 /** 204 * Returns all config definitions applicable to a given topic. 205 * 206 * @return a list of config definitions, possibly empty. 207 */ 208 private List<ConfigDefinition> getApplicableConfigDefinitions(Topic topic) { 209 List<ConfigDefinition> configDefs1 = lookupConfigDefinitions(ConfigTarget.SINGLETON.hashKey(topic)); 210 List<ConfigDefinition> configDefs2 = lookupConfigDefinitions(ConfigTarget.TYPE_INSTANCES.hashKey(topic)); 211 if (configDefs1 != null && configDefs2 != null) { 212 List<ConfigDefinition> configDefs = new ArrayList(); 213 configDefs.addAll(configDefs1); 214 configDefs.addAll(configDefs2); 215 return configDefs; 216 } 217 return configDefs1 != null ? configDefs1 : configDefs2 != null ? configDefs2 : new ArrayList(); 218 } 219 220 /** 221 * Returns the config definition for the given config type that is applicable to the given topic. 222 * 223 * @throws RuntimeException if no such config definition is registered. 224 */ 225 private ConfigDefinition getApplicableConfigDefinition(Topic topic, String configTypeUri) { 226 List<ConfigDefinition> configDefs = getApplicableConfigDefinitions(topic); 227 if (configDefs.size() == 0) { 228 throw new RuntimeException("None of the registered config definitions are applicable to " + info(topic)); 229 } 230 ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri); 231 if (configDef == null) { 232 throw new RuntimeException("For " + info(topic) + " no config definition for type \"" + configTypeUri + 233 "\" registered"); 234 } 235 return configDef; 236 } 237 238 // --- 239 240 private boolean isRegistered(ConfigDefinition configDef) { 241 for (List<ConfigDefinition> configDefs : registry.values()) { 242 if (configDefs.contains(configDef)) { 243 return true; 244 } 245 } 246 return false; 247 } 248 249 private ConfigDefinition findByConfigTypeUri(List<ConfigDefinition> configDefs, String configTypeUri) { 250 for (ConfigDefinition configDef : configDefs) { 251 if (configDef.getConfigTypeUri().equals(configTypeUri)) { 252 return configDef; 253 } 254 } 255 return null; 256 } 257 258 private List<ConfigDefinition> lookupConfigDefinitions(String hashKey) { 259 return registry.get(hashKey); 260 } 261 262 // --- 263 264 private String info(Topic topic) { 265 return "topic " + topic.getId() + " (value=\"" + topic.getSimpleValue() + "\", typeUri=\"" + 266 topic.getTypeUri() + "\", uri=\"" + topic.getUri() + "\")"; 267 } 268}