001/* 002 * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 * $Id: ActionService.java 28460 2008-01-03 15:34:05Z sfermigier $ 020 */ 021 022package org.nuxeo.ecm.platform.actions; 023 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.List; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 031import org.nuxeo.runtime.api.Framework; 032import org.nuxeo.runtime.metrics.MetricsService; 033import org.nuxeo.runtime.model.ComponentContext; 034import org.nuxeo.runtime.model.ComponentInstance; 035import org.nuxeo.runtime.model.ComponentName; 036import org.nuxeo.runtime.model.DefaultComponent; 037import org.nuxeo.runtime.services.config.ConfigurationService; 038 039import com.codahale.metrics.MetricRegistry; 040import com.codahale.metrics.SharedMetricRegistries; 041import com.codahale.metrics.Timer; 042 043/** 044 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 045 */ 046public class ActionService extends DefaultComponent implements ActionManager { 047 048 public static final ComponentName ID = new ComponentName("org.nuxeo.ecm.platform.actions.ActionService"); 049 050 private static final long serialVersionUID = -5256555810901945824L; 051 052 private static final Log log = LogFactory.getLog(ActionService.class); 053 054 private ActionContributionHandler actions; 055 056 private FilterContributionHandler filters; 057 058 protected final MetricRegistry metrics = SharedMetricRegistries.getOrCreate(MetricsService.class.getName()); 059 060 private static final String LOG_MIN_DURATION_KEY = "nuxeo.actions.debug.log_min_duration_ms"; 061 062 private long LOG_MIN_DURATION_NS = -1 * 1000000; 063 064 private Timer actionsTimer; 065 066 private Timer actionTimer; 067 068 private Timer filtersTimer; 069 070 private Timer filterTimer; 071 072 @Override 073 public void activate(ComponentContext context) { 074 filters = new FilterContributionHandler(); 075 actions = new ActionContributionHandler(filters); 076 actionsTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "actions")); 077 actionTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "action")); 078 filtersTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "filters")); 079 filterTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "filter")); 080 } 081 082 @Override 083 public void deactivate(ComponentContext context) { 084 actions = null; 085 filters = null; 086 actionsTimer = null; 087 actionTimer = null; 088 filtersTimer = null; 089 filterTimer = null; 090 } 091 092 @Override 093 public void applicationStarted(ComponentContext context) { 094 LOG_MIN_DURATION_NS = Long.parseLong( 095 Framework.getService(ConfigurationService.class).getProperty(LOG_MIN_DURATION_KEY, "-1")) * 1000000; 096 } 097 098 /** 099 * Return the action registry 100 * 101 * @deprecated since 5.5: use interface methods on ActionManager instead of public methods on ActionService. 102 */ 103 @Deprecated 104 public final ActionRegistry getActionRegistry() { 105 return actions.getRegistry(); 106 } 107 108 /** 109 * Return the action filter registry 110 * 111 * @deprecated since 5.5: use interface methods on ActionManager instead of public methods on ActionService. 112 */ 113 @Deprecated 114 public final ActionFilterRegistry getFilterRegistry() { 115 return filters.getRegistry(); 116 } 117 118 private void applyFilters(ActionContext context, List<Action> actions) { 119 Iterator<Action> it = actions.iterator(); 120 while (it.hasNext()) { 121 Action action = it.next(); 122 action.setFiltered(true); 123 if (!checkFilters(context, action)) { 124 it.remove(); 125 } 126 } 127 } 128 129 @Override 130 public boolean checkFilters(Action action, ActionContext context) { 131 return checkFilters(context, action); 132 } 133 134 private boolean checkFilters(ActionContext context, Action action) { 135 if (action == null) { 136 return false; 137 } 138 if (log.isTraceEnabled()) { 139 log.trace(String.format("Checking access for action '%s'...", action.getId())); 140 } 141 142 boolean granted = checkFilters(action, action.getFilterIds(), context); 143 if (granted) { 144 if (log.isTraceEnabled()) { 145 log.trace(String.format("Granting access for action '%s'", action.getId())); 146 } 147 } else { 148 if (log.isTraceEnabled()) { 149 log.trace(String.format("Denying access for action '%s'", action.getId())); 150 } 151 } 152 return granted; 153 } 154 155 @Override 156 public List<Action> getActions(String category, ActionContext context) { 157 return getActions(category, context, true); 158 } 159 160 @Override 161 public List<Action> getAllActions(String category) { 162 return getActionRegistry().getActions(category); 163 } 164 165 @Override 166 public List<Action> getActions(String category, ActionContext context, boolean hideUnavailableActions) { 167 final Timer.Context timerContext = actionsTimer.time(); 168 try { 169 List<Action> actions = getActionRegistry().getActions(category); 170 if (hideUnavailableActions) { 171 applyFilters(context, actions); 172 return actions; 173 } else { 174 List<Action> allActions = new ArrayList<Action>(); 175 allActions.addAll(actions); 176 applyFilters(context, actions); 177 178 for (Action a : allActions) { 179 a.setAvailable(actions.contains(a)); 180 } 181 return allActions; 182 } 183 } finally { 184 long duration = timerContext.stop(); 185 if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) { 186 log.debug(String.format("Resolving actions for category '%s' took: %.2f ms", category, 187 duration / 1000000.0)); 188 } 189 } 190 } 191 192 protected boolean isTimeTracerLogEnabled() { 193 return log.isDebugEnabled() && LOG_MIN_DURATION_NS >= 0; 194 } 195 196 @Override 197 public Action getAction(String actionId, ActionContext context, boolean hideUnavailableAction) { 198 final Timer.Context timerContext = actionTimer.time(); 199 try { 200 Action action = getActionRegistry().getAction(actionId); 201 if (action != null) { 202 if (hideUnavailableAction) { 203 if (!checkFilters(context, action)) { 204 return null; 205 } 206 } else { 207 if (!checkFilters(context, action)) { 208 action.setAvailable(false); 209 } 210 } 211 action.setFiltered(true); 212 } 213 return action; 214 } finally { 215 long duration = timerContext.stop(); 216 if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) { 217 log.debug(String.format("Resolving action with id '%s' took: %.2f ms", actionId, duration / 1000000.0)); 218 } 219 } 220 } 221 222 @Override 223 public Action getAction(String actionId) { 224 return getActionRegistry().getAction(actionId); 225 } 226 227 @Override 228 public boolean isRegistered(String actionId) { 229 return getActionRegistry().getAction(actionId) != null; 230 } 231 232 @Override 233 public boolean isEnabled(String actionId, ActionContext context) { 234 Action action = getActionRegistry().getAction(actionId); 235 if (action != null) { 236 return isEnabled(action, context); 237 } 238 return false; 239 } 240 241 public boolean isEnabled(Action action, ActionContext context) { 242 ActionFilterRegistry filterReg = getFilterRegistry(); 243 for (String filterId : action.getFilterIds()) { 244 ActionFilter filter = filterReg.getFilter(filterId); 245 if (filter != null && !filter.accept(action, context)) { 246 return false; 247 } 248 } 249 return true; 250 } 251 252 @Override 253 public ActionFilter[] getFilters(String actionId) { 254 Action action = getActionRegistry().getAction(actionId); 255 if (action == null) { 256 return null; 257 } 258 ActionFilterRegistry filterReg = getFilterRegistry(); 259 List<String> filterIds = action.getFilterIds(); 260 if (filterIds != null && !filterIds.isEmpty()) { 261 ActionFilter[] filters = new ActionFilter[filterIds.size()]; 262 for (int i = 0; i < filters.length; i++) { 263 String filterId = filterIds.get(i); 264 filters[i] = filterReg.getFilter(filterId); 265 } 266 return filters; 267 } 268 return null; 269 } 270 271 @Override 272 public boolean checkFilter(String filterId, ActionContext context) { 273 final Timer.Context timerContext = filterTimer.time(); 274 try { 275 ActionFilterRegistry filterReg = getFilterRegistry(); 276 ActionFilter filter = filterReg.getFilter(filterId); 277 if (filter == null) { 278 return false; 279 } 280 return filter.accept(null, context); 281 } finally { 282 long duration = timerContext.stop(); 283 if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) { 284 log.debug(String.format("Resolving filter with id '%s' took: %.2f ms", filterId, duration / 1000000.0)); 285 } 286 } 287 } 288 289 @Override 290 public boolean checkFilters(List<String> filterIds, ActionContext context) { 291 return checkFilters(null, filterIds, context); 292 } 293 294 protected boolean checkFilters(Action action, List<String> filterIds, ActionContext context) { 295 if (filterIds == null || filterIds.isEmpty()) { 296 return true; 297 } 298 final Timer.Context timerContext = filtersTimer.time(); 299 try { 300 ActionFilterRegistry filterReg = getFilterRegistry(); 301 for (String filterId : filterIds) { 302 ActionFilter filter = filterReg.getFilter(filterId); 303 if (filter == null) { 304 continue; 305 } 306 if (!filter.accept(action, context)) { 307 // denying filter found => ignore following filters 308 if (log.isTraceEnabled()) { 309 log.trace(String.format("Filter '%s' denied access", filterId)); 310 } 311 return false; 312 } 313 if (log.isTraceEnabled()) { 314 log.trace(String.format("Filter '%s' granted access", filterId)); 315 } 316 } 317 return true; 318 } finally { 319 long duration = timerContext.stop(); 320 if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) { 321 log.debug(String.format("Resolving filters %s took: %.2f ms", filterIds, duration / 1000000.0)); 322 } 323 } 324 } 325 326 @Override 327 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 328 if ("actions".equals(extensionPoint)) { 329 actions.addContribution((Action) contribution); 330 } else if ("filters".equals(extensionPoint)) { 331 if (contribution.getClass() == FilterFactory.class) { 332 registerFilterFactory((FilterFactory) contribution); 333 } else { 334 filters.addContribution((DefaultActionFilter) contribution); 335 } 336 } else if ("typeCompatibility".equals(extensionPoint)) { 337 actions.getRegistry().getTypeCategoryRelations().add((TypeCompatibility) contribution); 338 } 339 } 340 341 @Override 342 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 343 if ("actions".equals(extensionPoint)) { 344 actions.removeContribution((Action) contribution); 345 } else if ("filters".equals(extensionPoint)) { 346 if (contribution.getClass() == FilterFactory.class) { 347 unregisterFilterFactory((FilterFactory) contribution); 348 } else { 349 filters.removeContribution((DefaultActionFilter) contribution); 350 } 351 } 352 } 353 354 /** 355 * @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done 356 * @param ff 357 */ 358 @Deprecated 359 protected void registerFilterFactory(FilterFactory ff) { 360 getFilterRegistry().removeFilter(ff.id); 361 try { 362 ActionFilter filter = (ActionFilter) Thread.currentThread() 363 .getContextClassLoader() 364 .loadClass(ff.className) 365 .newInstance(); 366 filter.setId(ff.id); 367 getFilterRegistry().addFilter(filter); 368 } catch (ReflectiveOperationException e) { 369 log.error("Failed to create action filter", e); 370 } 371 } 372 373 /** 374 * @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done 375 * @param ff 376 */ 377 @Deprecated 378 public void unregisterFilterFactory(FilterFactory ff) { 379 getFilterRegistry().removeFilter(ff.id); 380 } 381 382 @Override 383 public void remove() { 384 } 385 386}