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 static final long LOG_MIN_DURATION_NS = Long.parseLong( 063 Framework.getService(ConfigurationService.class).getProperty(LOG_MIN_DURATION_KEY, "-1")) * 1000000; 064 065 private Timer actionsTimer; 066 067 private Timer actionTimer; 068 069 private Timer filtersTimer; 070 071 private Timer filterTimer; 072 073 @Override 074 public void activate(ComponentContext context) { 075 filters = new FilterContributionHandler(); 076 actions = new ActionContributionHandler(filters); 077 actionsTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "ations")); 078 actionTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "action")); 079 filtersTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "filters")); 080 filterTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "filter")); 081 } 082 083 @Override 084 public void deactivate(ComponentContext context) { 085 actions = null; 086 filters = null; 087 actionsTimer = null; 088 actionTimer = null; 089 filtersTimer = null; 090 filterTimer = null; 091 } 092 093 /** 094 * Return the action registry 095 * 096 * @deprecated since 5.5: use interface methods on ActionManager instead of public methods on ActionService. 097 */ 098 @Deprecated 099 public final ActionRegistry getActionRegistry() { 100 return actions.getRegistry(); 101 } 102 103 /** 104 * Return the action filter registry 105 * 106 * @deprecated since 5.5: use interface methods on ActionManager instead of public methods on ActionService. 107 */ 108 @Deprecated 109 public final ActionFilterRegistry getFilterRegistry() { 110 return filters.getRegistry(); 111 } 112 113 private void applyFilters(ActionContext context, List<Action> actions) { 114 Iterator<Action> it = actions.iterator(); 115 while (it.hasNext()) { 116 Action action = it.next(); 117 action.setFiltered(true); 118 if (!checkFilters(context, action)) { 119 it.remove(); 120 } 121 } 122 } 123 124 @Override 125 public boolean checkFilters(Action action, ActionContext context) { 126 return checkFilters(context, action); 127 } 128 129 private boolean checkFilters(ActionContext context, Action action) { 130 if (action == null) { 131 return false; 132 } 133 if (log.isDebugEnabled()) { 134 log.debug(String.format("Checking access for action '%s'...", action.getId())); 135 } 136 137 boolean granted = checkFilters(action, action.getFilterIds(), context); 138 if (granted) { 139 if (log.isDebugEnabled()) { 140 log.debug(String.format("Granting access for action '%s'", action.getId())); 141 } 142 } else { 143 if (log.isDebugEnabled()) { 144 log.debug(String.format("Denying access for action '%s'", action.getId())); 145 } 146 } 147 return granted; 148 } 149 150 @Override 151 public List<Action> getActions(String category, ActionContext context) { 152 return getActions(category, context, true); 153 } 154 155 @Override 156 public List<Action> getAllActions(String category) { 157 return getActionRegistry().getActions(category); 158 } 159 160 @Override 161 public List<Action> getActions(String category, ActionContext context, boolean hideUnavailableActions) { 162 final Timer.Context timerContext = actionsTimer.time(); 163 try { 164 List<Action> actions = getActionRegistry().getActions(category); 165 if (hideUnavailableActions) { 166 applyFilters(context, actions); 167 return actions; 168 } else { 169 List<Action> allActions = new ArrayList<Action>(); 170 allActions.addAll(actions); 171 applyFilters(context, actions); 172 173 for (Action a : allActions) { 174 a.setAvailable(actions.contains(a)); 175 } 176 return allActions; 177 } 178 } finally { 179 long duration = timerContext.stop(); 180 if ((LOG_MIN_DURATION_NS >= 0) && (duration > LOG_MIN_DURATION_NS)) { 181 log.info(String.format("Resolving actions for category '%s' took: %.2f ms", category, 182 duration / 1000000.0)); 183 } 184 } 185 } 186 187 @Override 188 public Action getAction(String actionId, ActionContext context, boolean hideUnavailableAction) { 189 final Timer.Context timerContext = actionTimer.time(); 190 try { 191 Action action = getActionRegistry().getAction(actionId); 192 if (action != null) { 193 if (hideUnavailableAction) { 194 if (!checkFilters(context, action)) { 195 return null; 196 } 197 } else { 198 if (!checkFilters(context, action)) { 199 action.setAvailable(false); 200 } 201 } 202 } 203 204 action.setFiltered(true); 205 return action; 206 } finally { 207 long duration = timerContext.stop(); 208 if ((LOG_MIN_DURATION_NS >= 0) && (duration > LOG_MIN_DURATION_NS)) { 209 log.info(String.format("Resolving action with id '%s' took: %.2f ms", actionId, duration / 1000000.0)); 210 } 211 } 212 } 213 214 @Override 215 public Action getAction(String actionId) { 216 return getActionRegistry().getAction(actionId); 217 } 218 219 @Override 220 public boolean isRegistered(String actionId) { 221 return getActionRegistry().getAction(actionId) != null; 222 } 223 224 @Override 225 public boolean isEnabled(String actionId, ActionContext context) { 226 Action action = getActionRegistry().getAction(actionId); 227 if (action != null) { 228 return isEnabled(action, context); 229 } 230 return false; 231 } 232 233 public boolean isEnabled(Action action, ActionContext context) { 234 ActionFilterRegistry filterReg = getFilterRegistry(); 235 for (String filterId : action.getFilterIds()) { 236 ActionFilter filter = filterReg.getFilter(filterId); 237 if (filter != null && !filter.accept(action, context)) { 238 return false; 239 } 240 } 241 return true; 242 } 243 244 @Override 245 public ActionFilter[] getFilters(String actionId) { 246 Action action = getActionRegistry().getAction(actionId); 247 if (action == null) { 248 return null; 249 } 250 ActionFilterRegistry filterReg = getFilterRegistry(); 251 List<String> filterIds = action.getFilterIds(); 252 if (filterIds != null && !filterIds.isEmpty()) { 253 ActionFilter[] filters = new ActionFilter[filterIds.size()]; 254 for (int i = 0; i < filters.length; i++) { 255 String filterId = filterIds.get(i); 256 filters[i] = filterReg.getFilter(filterId); 257 } 258 return filters; 259 } 260 return null; 261 } 262 263 @Override 264 public boolean checkFilter(String filterId, ActionContext context) { 265 final Timer.Context timerContext = filterTimer.time(); 266 try { 267 ActionFilterRegistry filterReg = getFilterRegistry(); 268 ActionFilter filter = filterReg.getFilter(filterId); 269 if (filter == null) { 270 return false; 271 } 272 return filter.accept(null, context); 273 } finally { 274 long duration = timerContext.stop(); 275 if ((LOG_MIN_DURATION_NS >= 0) && (duration > LOG_MIN_DURATION_NS)) { 276 log.info(String.format("Resolving filter with id '%s' took: %.2f ms", filterId, duration / 1000000.0)); 277 } 278 } 279 } 280 281 @Override 282 public boolean checkFilters(List<String> filterIds, ActionContext context) { 283 return checkFilters(null, filterIds, context); 284 } 285 286 protected boolean checkFilters(Action action, List<String> filterIds, ActionContext context) { 287 final Timer.Context timerContext = filtersTimer.time(); 288 try { 289 ActionFilterRegistry filterReg = getFilterRegistry(); 290 for (String filterId : filterIds) { 291 ActionFilter filter = filterReg.getFilter(filterId); 292 if (filter == null) { 293 continue; 294 } 295 if (!filter.accept(action, context)) { 296 // denying filter found => ignore following filters 297 if (log.isDebugEnabled()) { 298 log.debug(String.format("Filter '%s' denied access", filterId)); 299 } 300 return false; 301 } 302 if (log.isDebugEnabled()) { 303 log.debug(String.format("Filter '%s' granted access", filterId)); 304 } 305 } 306 return true; 307 } finally { 308 long duration = timerContext.stop(); 309 if ((LOG_MIN_DURATION_NS >= 0) && (duration > LOG_MIN_DURATION_NS)) { 310 log.info(String.format("Resolving filters %s took: %.2f ms", filterIds, duration / 1000000.0)); 311 } 312 } 313 } 314 315 @Override 316 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 317 if ("actions".equals(extensionPoint)) { 318 actions.addContribution((Action) contribution); 319 } else if ("filters".equals(extensionPoint)) { 320 if (contribution.getClass() == FilterFactory.class) { 321 registerFilterFactory((FilterFactory) contribution); 322 } else { 323 filters.addContribution((DefaultActionFilter) contribution); 324 } 325 } else if ("typeCompatibility".equals(extensionPoint)) { 326 actions.getRegistry().getTypeCategoryRelations().add((TypeCompatibility) contribution); 327 } 328 } 329 330 @Override 331 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 332 if ("actions".equals(extensionPoint)) { 333 actions.removeContribution((Action) contribution); 334 } else if ("filters".equals(extensionPoint)) { 335 if (contribution.getClass() == FilterFactory.class) { 336 unregisterFilterFactory((FilterFactory) contribution); 337 } else { 338 filters.removeContribution((DefaultActionFilter) contribution); 339 } 340 } 341 } 342 343 /** 344 * @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done 345 * @param ff 346 */ 347 @Deprecated 348 protected void registerFilterFactory(FilterFactory ff) { 349 getFilterRegistry().removeFilter(ff.id); 350 try { 351 ActionFilter filter = (ActionFilter) Thread.currentThread() 352 .getContextClassLoader() 353 .loadClass(ff.className) 354 .newInstance(); 355 filter.setId(ff.id); 356 getFilterRegistry().addFilter(filter); 357 } catch (ReflectiveOperationException e) { 358 log.error("Failed to create action filter", e); 359 } 360 } 361 362 /** 363 * @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done 364 * @param ff 365 */ 366 @Deprecated 367 public void unregisterFilterFactory(FilterFactory ff) { 368 getFilterRegistry().removeFilter(ff.id); 369 } 370 371 @Override 372 public void remove() { 373 } 374 375}