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", "ations")); 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 } 212 213 action.setFiltered(true); 214 return action; 215 } finally { 216 long duration = timerContext.stop(); 217 if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) { 218 log.debug(String.format("Resolving action with id '%s' took: %.2f ms", actionId, duration / 1000000.0)); 219 } 220 } 221 } 222 223 @Override 224 public Action getAction(String actionId) { 225 return getActionRegistry().getAction(actionId); 226 } 227 228 @Override 229 public boolean isRegistered(String actionId) { 230 return getActionRegistry().getAction(actionId) != null; 231 } 232 233 @Override 234 public boolean isEnabled(String actionId, ActionContext context) { 235 Action action = getActionRegistry().getAction(actionId); 236 if (action != null) { 237 return isEnabled(action, context); 238 } 239 return false; 240 } 241 242 public boolean isEnabled(Action action, ActionContext context) { 243 ActionFilterRegistry filterReg = getFilterRegistry(); 244 for (String filterId : action.getFilterIds()) { 245 ActionFilter filter = filterReg.getFilter(filterId); 246 if (filter != null && !filter.accept(action, context)) { 247 return false; 248 } 249 } 250 return true; 251 } 252 253 @Override 254 public ActionFilter[] getFilters(String actionId) { 255 Action action = getActionRegistry().getAction(actionId); 256 if (action == null) { 257 return null; 258 } 259 ActionFilterRegistry filterReg = getFilterRegistry(); 260 List<String> filterIds = action.getFilterIds(); 261 if (filterIds != null && !filterIds.isEmpty()) { 262 ActionFilter[] filters = new ActionFilter[filterIds.size()]; 263 for (int i = 0; i < filters.length; i++) { 264 String filterId = filterIds.get(i); 265 filters[i] = filterReg.getFilter(filterId); 266 } 267 return filters; 268 } 269 return null; 270 } 271 272 @Override 273 public boolean checkFilter(String filterId, ActionContext context) { 274 final Timer.Context timerContext = filterTimer.time(); 275 try { 276 ActionFilterRegistry filterReg = getFilterRegistry(); 277 ActionFilter filter = filterReg.getFilter(filterId); 278 if (filter == null) { 279 return false; 280 } 281 return filter.accept(null, context); 282 } finally { 283 long duration = timerContext.stop(); 284 if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) { 285 log.debug(String.format("Resolving filter with id '%s' took: %.2f ms", filterId, duration / 1000000.0)); 286 } 287 } 288 } 289 290 @Override 291 public boolean checkFilters(List<String> filterIds, ActionContext context) { 292 return checkFilters(null, filterIds, context); 293 } 294 295 protected boolean checkFilters(Action action, List<String> filterIds, ActionContext context) { 296 final Timer.Context timerContext = filtersTimer.time(); 297 try { 298 ActionFilterRegistry filterReg = getFilterRegistry(); 299 for (String filterId : filterIds) { 300 ActionFilter filter = filterReg.getFilter(filterId); 301 if (filter == null) { 302 continue; 303 } 304 if (!filter.accept(action, context)) { 305 // denying filter found => ignore following filters 306 if (log.isTraceEnabled()) { 307 log.trace(String.format("Filter '%s' denied access", filterId)); 308 } 309 return false; 310 } 311 if (log.isTraceEnabled()) { 312 log.trace(String.format("Filter '%s' granted access", filterId)); 313 } 314 } 315 return true; 316 } finally { 317 long duration = timerContext.stop(); 318 if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) { 319 log.debug(String.format("Resolving filters %s took: %.2f ms", filterIds, duration / 1000000.0)); 320 } 321 } 322 } 323 324 @Override 325 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 326 if ("actions".equals(extensionPoint)) { 327 actions.addContribution((Action) contribution); 328 } else if ("filters".equals(extensionPoint)) { 329 if (contribution.getClass() == FilterFactory.class) { 330 registerFilterFactory((FilterFactory) contribution); 331 } else { 332 filters.addContribution((DefaultActionFilter) contribution); 333 } 334 } else if ("typeCompatibility".equals(extensionPoint)) { 335 actions.getRegistry().getTypeCategoryRelations().add((TypeCompatibility) contribution); 336 } 337 } 338 339 @Override 340 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 341 if ("actions".equals(extensionPoint)) { 342 actions.removeContribution((Action) contribution); 343 } else if ("filters".equals(extensionPoint)) { 344 if (contribution.getClass() == FilterFactory.class) { 345 unregisterFilterFactory((FilterFactory) contribution); 346 } else { 347 filters.removeContribution((DefaultActionFilter) contribution); 348 } 349 } 350 } 351 352 /** 353 * @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done 354 * @param ff 355 */ 356 @Deprecated 357 protected void registerFilterFactory(FilterFactory ff) { 358 getFilterRegistry().removeFilter(ff.id); 359 try { 360 ActionFilter filter = (ActionFilter) Thread.currentThread() 361 .getContextClassLoader() 362 .loadClass(ff.className) 363 .newInstance(); 364 filter.setId(ff.id); 365 getFilterRegistry().addFilter(filter); 366 } catch (ReflectiveOperationException e) { 367 log.error("Failed to create action filter", e); 368 } 369 } 370 371 /** 372 * @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done 373 * @param ff 374 */ 375 @Deprecated 376 public void unregisterFilterFactory(FilterFactory ff) { 377 getFilterRegistry().removeFilter(ff.id); 378 } 379 380 @Override 381 public void remove() { 382 } 383 384}