001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements.  See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing, software
013 *  distributed under the License is distributed on an "AS IS" BASIS,
014 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 *  See the License for the specific language governing permissions and
016 *  limitations under the License.
017 */
018package org.apache.activemq.karaf.commands;
019
020import java.lang.reflect.Field;
021import java.lang.reflect.Type;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.felix.gogo.commands.Action;
029import org.apache.felix.gogo.commands.Argument;
030import org.apache.felix.gogo.commands.basic.AbstractCommand;
031import org.apache.felix.gogo.commands.basic.ActionPreparator;
032import org.apache.felix.gogo.commands.basic.DefaultActionPreparator;
033import org.apache.felix.service.command.CommandSession;
034import org.apache.karaf.shell.console.BlueprintContainerAware;
035import org.apache.karaf.shell.console.BundleContextAware;
036import org.apache.karaf.shell.console.CompletableFunction;
037import org.apache.karaf.shell.console.Completer;
038import org.apache.karaf.shell.console.commands.GenericType;
039import org.osgi.framework.BundleContext;
040import org.osgi.service.blueprint.container.BlueprintContainer;
041import org.osgi.service.blueprint.container.Converter;
042
043/**
044 * Base command to process options and wrap native ActiveMQ console commands.
045 */
046public class ActiveMQCommand extends AbstractCommand implements CompletableFunction
047{
048    protected BlueprintContainer blueprintContainer;
049    protected Converter blueprintConverter;
050    protected String actionId;
051    protected List<Completer> completers;
052
053    public void setBlueprintContainer(BlueprintContainer blueprintContainer) {
054        this.blueprintContainer = blueprintContainer;
055    }
056
057    public void setBlueprintConverter(Converter blueprintConverter) {
058        this.blueprintConverter = blueprintConverter;
059    }
060
061    public void setActionId(String actionId) {
062        this.actionId = actionId;
063    }
064
065    @Override
066    public List<Completer> getCompleters() {
067        return completers;
068    }
069
070    public void setCompleters(List<Completer> completers) {
071        this.completers = completers;
072    }
073
074    @Override
075    protected ActionPreparator getPreparator() throws Exception {
076        return new ActiveMQActionPreparator();
077    }
078
079    class ActiveMQActionPreparator extends DefaultActionPreparator {
080        @Override
081        public boolean prepare(Action action, CommandSession session, List<Object> params) throws Exception
082        {
083            Map<Argument, Field> arguments = new HashMap<Argument, Field>();
084            List<Argument> orderedArguments = new ArrayList<Argument>();
085            // Introspect
086            for (Class type = action.getClass(); type != null; type = type.getSuperclass()) {
087                for (Field field : type.getDeclaredFields()) {
088                    Argument argument = field.getAnnotation(Argument.class);
089                    if (argument != null) {
090                        arguments.put(argument, field);
091                        int index = argument.index();
092                        while (orderedArguments.size() <= index) {
093                            orderedArguments.add(null);
094                        }
095                        if (orderedArguments.get(index) != null) {
096                            throw new IllegalArgumentException("Duplicate argument index: " + index);
097                        }
098                        orderedArguments.set(index, argument);
099                    }
100                }
101            }
102            // Check indexes are correct
103            for (int i = 0; i < orderedArguments.size(); i++) {
104                if (orderedArguments.get(i) == null) {
105                    throw new IllegalArgumentException("Missing argument for index: " + i);
106                }
107            }
108            // Populate
109            Map<Argument, Object> argumentValues = new HashMap<Argument, Object>();
110            int argIndex = 0;
111            for (Iterator<Object> it = params.iterator(); it.hasNext();) {
112                Object param = it.next();
113                if (argIndex >= orderedArguments.size()) {
114                    throw new IllegalArgumentException("Too many arguments specified");
115                }
116                Argument argument = orderedArguments.get(argIndex);
117                if (!argument.multiValued()) {
118                    argIndex++;
119                }
120                if (argument.multiValued()) {
121                    List<Object> l = (List<Object>) argumentValues.get(argument);
122                    if (l == null) {
123                        l = new ArrayList<Object>();
124                        argumentValues.put(argument, l);
125                    }
126                    l.add(param);
127                } else {
128                    argumentValues.put(argument, param);
129                }
130            }
131
132            if (argumentValues.size() == 1 && arguments.size() == 1) {
133                Object val = argumentValues.values().iterator().next();
134                // short circut convert via blueprint... cause all our commands match this
135                // bluepring was failing to convert the last long param to a string for browse
136                // where dest is a numeric value - activemq-karaf-itests
137                // see: org.apache.activemq.karaf.itest.ActiveMQBrokerFeatureTest.test()
138                if (val instanceof List) {
139                    Field field = arguments.values().iterator().next();
140                    List<Object> values = (List<Object>) val;
141                    ArrayList<String> convertedValues = new ArrayList<String>(values.size());
142                    for (Object o : values) {
143                        convertedValues.add(String.valueOf(o));
144                    }
145                    field.setAccessible(true);
146                    field.set(action, convertedValues);
147                    return true;
148                }
149            }
150
151            for (Map.Entry<Argument, Object> entry : argumentValues.entrySet()) {
152                Field field = arguments.get(entry.getKey());
153                Object value = convert(action, session, entry.getValue(), field.getGenericType());
154                field.setAccessible(true);
155                field.set(action, value);
156            }
157            return true;
158        }
159
160        @Override
161        protected Object convert(Action action, CommandSession commandSession, Object o, Type type) throws Exception {
162            return blueprintConverter.convert(o, new GenericType(type));
163        }
164    }
165
166    @Override
167    public Action createNewAction() {
168        Action action = (Action) blueprintContainer.getComponentInstance(actionId);
169        if (action instanceof BlueprintContainerAware) {
170            ((BlueprintContainerAware) action).setBlueprintContainer(blueprintContainer);
171        }
172        if (action instanceof BundleContextAware) {
173            BundleContext context = (BundleContext) blueprintContainer.getComponentInstance("blueprintBundleContext");
174            ((BundleContextAware) action).setBundleContext(context);
175        }
176        return action;
177    }
178
179    @Override
180    public Map<String, Completer> getOptionalCompleters() {
181        //TODO implement completers
182        return null;
183    }
184}