/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nuxeo - initial API and implementation
*/
package org.nuxeo.ecm.platform.actions;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.platform.actions.ejb.ActionManager;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.metrics.MetricsService;
import org.nuxeo.runtime.model.ComponentContext;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.ComponentName;
import org.nuxeo.runtime.model.DefaultComponent;
import org.nuxeo.runtime.services.config.ConfigurationService;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.Timer;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class ActionService extends DefaultComponent implements ActionManager {
public static final ComponentName ID = new ComponentName("org.nuxeo.ecm.platform.actions.ActionService");
private static final long serialVersionUID = -5256555810901945824L;
private static final Log log = LogFactory.getLog(ActionService.class);
private ActionContributionHandler actions;
private FilterContributionHandler filters;
protected final MetricRegistry metrics = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
private static final String LOG_MIN_DURATION_KEY = "nuxeo.actions.debug.log_min_duration_ms";
private long LOG_MIN_DURATION_NS = -1 * 1000000;
private Timer actionsTimer;
private Timer actionTimer;
private Timer filtersTimer;
private Timer filterTimer;
@Override
public void activate(ComponentContext context) {
filters = new FilterContributionHandler();
actions = new ActionContributionHandler(filters);
actionsTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "actions"));
actionTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "action"));
filtersTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "filters"));
filterTimer = metrics.timer(MetricRegistry.name("nuxeo", "ActionService", "filter"));
}
@Override
public void deactivate(ComponentContext context) {
actions = null;
filters = null;
actionsTimer = null;
actionTimer = null;
filtersTimer = null;
filterTimer = null;
}
@Override
public void applicationStarted(ComponentContext context) {
LOG_MIN_DURATION_NS = Long.parseLong(
Framework.getService(ConfigurationService.class).getProperty(LOG_MIN_DURATION_KEY, "-1")) * 1000000;
}
/**
* Return the action registry
*/
// used by unit test
protected final ActionRegistry getActionRegistry() {
return actions.getRegistry();
}
/**
* Return the action filter registry
*/
// used by unit test
protected final ActionFilterRegistry getFilterRegistry() {
return filters.getRegistry();
}
private void applyFilters(ActionContext context, List<Action> actions) {
Iterator<Action> it = actions.iterator();
while (it.hasNext()) {
Action action = it.next();
action.setFiltered(true);
if (!checkFilters(context, action)) {
it.remove();
}
}
}
@Override
public boolean checkFilters(Action action, ActionContext context) {
return checkFilters(context, action);
}
private boolean checkFilters(ActionContext context, Action action) {
if (action == null) {
return false;
}
if (log.isTraceEnabled()) {
log.trace(String.format("Checking access for action '%s'...", action.getId()));
}
boolean granted = checkFilters(action, action.getFilterIds(), context);
if (granted) {
if (log.isTraceEnabled()) {
log.trace(String.format("Granting access for action '%s'", action.getId()));
}
} else {
if (log.isTraceEnabled()) {
log.trace(String.format("Denying access for action '%s'", action.getId()));
}
}
return granted;
}
@Override
public List<Action> getActions(String category, ActionContext context) {
return getActions(category, context, true);
}
@Override
public List<Action> getAllActions(String category) {
return getActionRegistry().getActions(category);
}
@Override
public List<Action> getActions(String category, ActionContext context, boolean hideUnavailableActions) {
final Timer.Context timerContext = actionsTimer.time();
try {
List<Action> actions = getActionRegistry().getActions(category);
if (hideUnavailableActions) {
applyFilters(context, actions);
return actions;
} else {
List<Action> allActions = new ArrayList<>();
allActions.addAll(actions);
applyFilters(context, actions);
for (Action a : allActions) {
a.setAvailable(actions.contains(a));
}
return allActions;
}
} finally {
long duration = timerContext.stop();
if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) {
log.debug(String.format("Resolving actions for category '%s' took: %.2f ms", category,
duration / 1000000.0));
}
}
}
protected boolean isTimeTracerLogEnabled() {
return log.isDebugEnabled() && LOG_MIN_DURATION_NS >= 0;
}
@Override
public Action getAction(String actionId, ActionContext context, boolean hideUnavailableAction) {
final Timer.Context timerContext = actionTimer.time();
try {
Action action = getActionRegistry().getAction(actionId);
if (action != null) {
if (hideUnavailableAction) {
if (!checkFilters(context, action)) {
return null;
}
} else {
if (!checkFilters(context, action)) {
action.setAvailable(false);
}
}
action.setFiltered(true);
}
return action;
} finally {
long duration = timerContext.stop();
if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) {
log.debug(String.format("Resolving action with id '%s' took: %.2f ms", actionId, duration / 1000000.0));
}
}
}
@Override
public Action getAction(String actionId) {
return getActionRegistry().getAction(actionId);
}
@Override
public boolean isRegistered(String actionId) {
return getActionRegistry().getAction(actionId) != null;
}
@Override
public boolean isEnabled(String actionId, ActionContext context) {
Action action = getActionRegistry().getAction(actionId);
if (action != null) {
return isEnabled(action, context);
}
return false;
}
public boolean isEnabled(Action action, ActionContext context) {
ActionFilterRegistry filterReg = getFilterRegistry();
for (String filterId : action.getFilterIds()) {
ActionFilter filter = filterReg.getFilter(filterId);
if (filter != null && !filter.accept(action, context)) {
return false;
}
}
return true;
}
@Override
public ActionFilter[] getFilters(String actionId) {
Action action = getActionRegistry().getAction(actionId);
if (action == null) {
return null;
}
ActionFilterRegistry filterReg = getFilterRegistry();
List<String> filterIds = action.getFilterIds();
if (filterIds != null && !filterIds.isEmpty()) {
ActionFilter[] filters = new ActionFilter[filterIds.size()];
for (int i = 0; i < filters.length; i++) {
String filterId = filterIds.get(i);
filters[i] = filterReg.getFilter(filterId);
}
return filters;
}
return null;
}
@Override
public ActionFilter getFilter(String filterId) {
return getFilterRegistry().getFilter(filterId);
}
@Override
public boolean checkFilter(String filterId, ActionContext context) {
final Timer.Context timerContext = filterTimer.time();
try {
ActionFilter filter = getFilter(filterId);
return filter != null && filter.accept(null, context);
} finally {
long duration = timerContext.stop();
if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) {
log.debug(String.format("Resolving filter with id '%s' took: %.2f ms", filterId, duration / 1000000.0));
}
}
}
@Override
public boolean checkFilters(List<String> filterIds, ActionContext context) {
return checkFilters(null, filterIds, context);
}
protected boolean checkFilters(Action action, List<String> filterIds, ActionContext context) {
if (filterIds == null || filterIds.isEmpty()) {
return true;
}
final Timer.Context timerContext = filtersTimer.time();
try {
ActionFilterRegistry filterReg = getFilterRegistry();
for (String filterId : filterIds) {
ActionFilter filter = filterReg.getFilter(filterId);
if (filter == null) {
continue;
}
if (!filter.accept(action, context)) {
// denying filter found => ignore following filters
if (log.isTraceEnabled()) {
log.trace(String.format("Filter '%s' denied access", filterId));
}
return false;
}
if (log.isTraceEnabled()) {
log.trace(String.format("Filter '%s' granted access", filterId));
}
}
return true;
} finally {
long duration = timerContext.stop();
if (isTimeTracerLogEnabled() && (duration > LOG_MIN_DURATION_NS)) {
log.debug(String.format("Resolving filters %s took: %.2f ms", filterIds, duration / 1000000.0));
}
}
}
@Override
public void addAction(Action action) {
getActionRegistry().addAction(action);
}
@Override
public Action removeAction(String actionId) {
return getActionRegistry().removeAction(actionId);
}
@Override
public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if ("actions".equals(extensionPoint)) {
actions.addContribution((Action) contribution);
} else if ("filters".equals(extensionPoint)) {
if (contribution.getClass() == FilterFactory.class) {
registerFilterFactory((FilterFactory) contribution);
} else {
filters.addContribution((DefaultActionFilter) contribution);
}
} else if ("typeCompatibility".equals(extensionPoint)) {
actions.getRegistry().getTypeCategoryRelations().add((TypeCompatibility) contribution);
}
}
@Override
public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if ("actions".equals(extensionPoint)) {
actions.removeContribution((Action) contribution);
} else if ("filters".equals(extensionPoint)) {
if (contribution.getClass() == FilterFactory.class) {
unregisterFilterFactory((FilterFactory) contribution);
} else {
filters.removeContribution((DefaultActionFilter) contribution);
}
}
}
/**
* @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done
* @param ff
*/
@Deprecated
protected void registerFilterFactory(FilterFactory ff) {
getFilterRegistry().removeFilter(ff.id);
try {
ActionFilter filter = (ActionFilter) Thread.currentThread()
.getContextClassLoader()
.loadClass(ff.className)
.newInstance();
filter.setId(ff.id);
getFilterRegistry().addFilter(filter);
} catch (ReflectiveOperationException e) {
log.error("Failed to create action filter", e);
}
}
/**
* @deprecated seems not used in Nuxeo - should be removed - and anyway the merge is not done
* @param ff
*/
@Deprecated
public void unregisterFilterFactory(FilterFactory ff) {
getFilterRegistry().removeFilter(ff.id);
}
@Override
public void remove() {
}
}