/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * bstefanescu */ package org.eclipse.ecr.automation.core.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.ecr.automation.AdapterNotFoundException; import org.eclipse.ecr.automation.AutomationService; import org.eclipse.ecr.automation.CompiledChain; import org.eclipse.ecr.automation.InvalidChainException; import org.eclipse.ecr.automation.OperationChain; import org.eclipse.ecr.automation.OperationContext; import org.eclipse.ecr.automation.OperationDocumentation; import org.eclipse.ecr.automation.OperationException; import org.eclipse.ecr.automation.OperationNotFoundException; import org.eclipse.ecr.automation.OperationParameters; import org.eclipse.ecr.automation.OperationType; import org.eclipse.ecr.automation.TypeAdapter; import org.eclipse.ecr.automation.core.annotations.Operation; /** * The operation registry is thread safe and optimized for modifications at * startup and lookups at runtime. * * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class OperationServiceImpl implements AutomationService { /** * Modifiable operation registry. Modifying the registry is using a lock and * it's thread safe. Modifications are removing the cache. */ protected final Map<String, OperationTypeImpl> operations; /** * Read only cache for operation lookup. Thread safe. Not using * synchronization if cache already created. */ protected volatile Map<String, OperationTypeImpl> lookup; /** * Modifiable chain registry */ protected final Map<String, ChainEntry> chains; /** * Read only cache for managed chains */ protected volatile Map<String, ChainEntry> chainLookup; /** * Adapter registry */ protected AdapterKeyedRegistry adapters; public OperationServiceImpl() { operations = new HashMap<String, OperationTypeImpl>(); chains = new HashMap<String, ChainEntry>(); adapters = new AdapterKeyedRegistry(); } public Object run(OperationContext ctx, String chainId) throws OperationException, InvalidChainException, Exception { try { Object input = ctx.getInput(); Class<?> inputType = input == null ? Void.TYPE : input.getClass(); ChainEntry chain = getChainEntry(chainId); if (chain.cchain == null) { chain.cchain = compileChain(inputType, chain.chain); } Object ret = chain.cchain.invoke(ctx); if (ctx.getCoreSession() != null && ctx.isCommit()) { // auto save session if any ctx.getCoreSession().save(); } return ret; } finally { ctx.dispose(); } } public Object run(OperationContext ctx, OperationChain chain) throws OperationException, InvalidChainException, Exception { try { Object input = ctx.getInput(); Class<?> inputType = input == null ? Void.TYPE : input.getClass(); Object ret = compileChain(inputType, chain).invoke(ctx); if (ctx.getCoreSession() != null && ctx.isCommit()) { // auto save session if any ctx.getCoreSession().save(); } return ret; } finally { ctx.dispose(); } } public synchronized void putOperationChain(OperationChain chain) throws OperationException { putOperationChain(chain, false); } public synchronized void putOperationChain(OperationChain chain, boolean replace) throws OperationException { if (!replace && chains.containsKey(chain.getId())) { throw new OperationException("Chain with id " + chain.getId() + " already exists"); } chains.put(chain.getId(), new ChainEntry(chain)); chainLookup = null; } public synchronized void removeOperationChain(String id) { if (chains.remove(id) != null) { chainLookup = null; } } public OperationChain getOperationChain(String id) throws OperationNotFoundException { ChainEntry chain = chainLookup().get(id); if (chain == null) { throw new OperationNotFoundException( "No such chain was registered: " + id); } return chain.chain; } public List<OperationChain> getOperationChains() { List<OperationChain> result = new ArrayList<OperationChain>(); Map<String, ChainEntry> chains = chainLookup(); for (ChainEntry entry : chains.values()) { result.add(entry.chain); } return result; } public ChainEntry getChainEntry(String id) throws OperationException { ChainEntry chain = chainLookup().get(id); if (chain == null) { throw new OperationException("No such chain was registered: " + id); } return chain; } public void putOperation(Class<?> type) throws OperationException { OperationTypeImpl op = new OperationTypeImpl(this, type); putOperation(op, false); } public void putOperation(Class<?> type, boolean replace) throws OperationException { OperationTypeImpl op = new OperationTypeImpl(this, type); putOperation(op, replace); } protected synchronized void putOperation(OperationTypeImpl op, boolean replace) throws OperationException { if (!replace && operations.containsKey(op.getId())) { throw new OperationException("An operation is already bound to: " + op.getId() + ". Use 'replace=true' to replace an existing operation"); } operations.put(op.getId(), op); lookup = null; } public synchronized void removeOperation(Class<?> key) { OperationType op = operations.remove(key.getAnnotation(Operation.class).id()); if (op != null) { operations.remove(op.getId()); lookup = null; } } public OperationType[] getOperations() { Collection<OperationTypeImpl> values = lookup().values(); return values.toArray(new OperationType[values.size()]); } public OperationType getOperation(String id) throws OperationNotFoundException { OperationType op = lookup().get(id); if (op == null) { throw new OperationNotFoundException( "No operation was bound on ID: " + id); } return op; } private Map<String, OperationTypeImpl> lookup() { Map<String, OperationTypeImpl> _lookup = lookup; if (_lookup == null) { synchronized (this) { lookup = new HashMap<String, OperationTypeImpl>(operations); _lookup = lookup; } } return _lookup; } private Map<String, ChainEntry> chainLookup() { Map<String, ChainEntry> _lookup = chainLookup; if (_lookup == null) { synchronized (this) { chainLookup = new HashMap<String, ChainEntry>(chains); _lookup = chainLookup; } } return _lookup; } public CompiledChain compileChain(Class<?> inputType, OperationChain chain) throws Exception, InvalidChainException { List<OperationParameters> ops = chain.getOperations(); return compileChain(inputType, ops.toArray(new OperationParameters[ops.size()])); } public CompiledChain compileChain(Class<?> inputType, OperationParameters... chain) throws Exception, InvalidChainException { return CompiledChainImpl.buildChain(this, inputType == null ? Void.TYPE : inputType, chain); } public void putTypeAdapter(Class<?> accept, Class<?> produce, TypeAdapter adapter) { adapters.put(new TypeAdapterKey(accept, produce), adapter); } public void removeTypeAdapter(Class<?> accept, Class<?> produce) { adapters.remove(new TypeAdapterKey(accept, produce)); } public TypeAdapter getTypeAdapter(Class<?> accept, Class<?> produce) { return adapters.get(new TypeAdapterKey(accept, produce)); } public boolean isTypeAdaptable(Class<?> typeToAdapt, Class<?> targetType) { return getTypeAdapter(typeToAdapt, targetType) != null; } @SuppressWarnings("unchecked") public <T> T getAdaptedValue(OperationContext ctx, Object toAdapt, Class<?> targetType) throws Exception { if (toAdapt == null) { return null; } // handle primitive types Class<?> toAdaptClass = toAdapt.getClass(); if (targetType.isPrimitive()) { targetType = getTypeForPrimitive(targetType); if (targetType.isAssignableFrom(toAdaptClass)) { return (T) toAdapt; } } TypeAdapter adapter = getTypeAdapter(toAdaptClass, targetType); if (adapter == null) { throw new AdapterNotFoundException("No type adapter found for input: " + toAdapt.getClass() + " and output " + targetType, ctx); } return (T) adapter.getAdaptedValue(ctx, toAdapt); } public List<OperationDocumentation> getDocumentation() { List<OperationDocumentation> result = new ArrayList<OperationDocumentation>(); Collection<OperationTypeImpl> ops = lookup().values(); for (OperationTypeImpl ot : ops.toArray(new OperationTypeImpl[ops.size()])) { result.add(ot.getDocumentation()); } Collections.sort(result); return result; } static class ChainEntry { OperationChain chain; CompiledChain cchain; ChainEntry(OperationChain chain) { this.chain = chain; } } public static Class<?> getTypeForPrimitive(Class<?> primitiveType) { if (primitiveType == Boolean.TYPE) { return Boolean.class; } else if (primitiveType == Integer.TYPE) { return Integer.class; } else if (primitiveType == Long.TYPE) { return Long.class; } else if (primitiveType == Float.TYPE) { return Float.class; } else if (primitiveType == Double.TYPE) { return Double.class; } else if (primitiveType == Character.TYPE) { return Character.class; } else if (primitiveType == Byte.TYPE) { return Byte.class; } else if (primitiveType == Short.TYPE) { return Short.class; } return primitiveType; } }