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