/**
* Copyright 2005-2014 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.
*/
package io.fabric8.forge.camel.commands;
import com.jcabi.xml.XML;
import com.jcabi.xml.XMLDocument;
import io.fabric8.forge.camel.api.CamelSupportedTechnologyEnum;
import org.apache.commons.io.FileUtils;
import org.jboss.forge.addon.dependencies.Dependency;
import org.jboss.forge.addon.dependencies.builder.DependencyBuilder;
import org.jboss.forge.addon.parser.java.facets.JavaSourceFacet;
import org.jboss.forge.addon.parser.java.resources.JavaResource;
import org.jboss.forge.addon.parser.java.resources.JavaResourceVisitor;
import org.jboss.forge.addon.projects.Project;
import org.jboss.forge.addon.projects.dependencies.DependencyInstaller;
import org.jboss.forge.addon.projects.facets.ResourcesFacet;
import org.jboss.forge.addon.resource.FileResource;
import org.jboss.forge.addon.resource.Resource;
import org.jboss.forge.addon.resource.ResourceFactory;
import org.jboss.forge.addon.resource.URLResource;
import org.jboss.forge.addon.resource.visit.VisitContext;
import org.jboss.forge.addon.rest.ClientFactory;
import org.jboss.forge.addon.templates.Template;
import org.jboss.forge.addon.templates.TemplateFactory;
import org.jboss.forge.addon.templates.freemarker.FreemarkerTemplate;
import org.jboss.forge.addon.ui.context.UIBuilder;
import org.jboss.forge.addon.ui.context.UIContext;
import org.jboss.forge.addon.ui.context.UIExecutionContext;
import org.jboss.forge.addon.ui.context.UIValidationContext;
import org.jboss.forge.addon.ui.input.InputComponent;
import org.jboss.forge.addon.ui.input.UICompleter;
import org.jboss.forge.addon.ui.input.UIInput;
import org.jboss.forge.addon.ui.input.UIPrompt;
import org.jboss.forge.addon.ui.metadata.UICommandMetadata;
import org.jboss.forge.addon.ui.metadata.WithAttributes;
import org.jboss.forge.addon.ui.result.Result;
import org.jboss.forge.addon.ui.result.Results;
import org.jboss.forge.addon.ui.util.Categories;
import org.jboss.forge.addon.ui.util.Metadata;
import org.jboss.forge.addon.ui.validate.UIValidator;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import org.jboss.forge.roaster.model.source.JavaSource;
import javax.inject.Inject;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.MediaType;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class CamelNewComponentsCommand extends AbstractCamelCommand {
//UIInput field names will automatically be the names for the command line flags
@Inject
@WithAttributes(label = "Camel components to add to the project", description = "Select the list of Camel component that you want to add to your project.", required = true)
UIInput<String> named;
@Inject
@WithAttributes(label = "Version", description = "Version.", required = true)
UIInput<String> version;
@Inject
private ClientFactory factory;
@Inject
TemplateFactory templateFactory;
@Inject
ResourceFactory resourceFactory;
@Inject
private DependencyInstaller dependencyInstaller;
Client client;
Set<String> availableComponents;
@Override
public UICommandMetadata getMetadata(UIContext context) {
return Metadata.forCommand(CamelNewComponentsCommand.class).name(
"camel-new-component").category(Categories.create(CATEGORY))
.description("Add new Camel components to the project.");
}
@Override
public void initializeUI(UIBuilder builder) throws Exception {
super.initializeUI(builder);
initializeAvailableCamelComponents();
//populate autocompletion options
named.setCompleter(new UICompleter<String>() {
@Override
public Iterable<String> getCompletionProposals(UIContext context, InputComponent<?, String> input, String value) {
// filter
List<String> filteredList = new ArrayList<String>(availableComponents.size());
for (String s : availableComponents) {
if (!camelDepsInUse.contains(s)) {
filteredList.add(s);
}
}
// rename key
List<String> noPrefixList = new ArrayList<String>(filteredList.size());
for (String s : filteredList) {
noPrefixList.add(s.split("-")[1]);
}
// keep only relevant
List<String> componentsList = new ArrayList<>(noPrefixList.size());
for (String s : noPrefixList) {
//adding to the proposals only entries relevant to the current partially typed string
if (s.startsWith(value))
componentsList.add(s);
}
//sort
Collections.sort(componentsList);
return componentsList;
}
});
version.setCompleter(new UIVersionCompleter(this));
named.addValidator(new UIValidator() {
@Override
public void validate(UIValidationContext context) {
if (availableComponents == null) {
availableComponents = initializeAvailableCamelComponents();
}
if (!availableComponents.contains("camel-" + context.getCurrentInputComponent().getValue())) {
context.addValidationError(context.getCurrentInputComponent(), "Selected Camel component is not available.");
}
}
});
builder.add(named).add(version);
}
@Override
public Result execute(UIExecutionContext context) throws Exception {
String component = named.getValue();
component = "camel-" + component;
String selectedVersion = version.getValue();
Project project = getSelectedProject(context);
installMavenDependency(component, selectedVersion, project);
CamelSupportedTechnologyEnum tech = null;
try {
tech = detectTechnology(project);
} catch (IllegalStateException e) {
return Results.fail("Unable to identify project type.");
}
//add different samples based on technology
switch (tech) {
case BLUEPRINT: {
ResourcesFacet facet = getSelectedProject(context).getFacet(ResourcesFacet.class);
String outputFilePath = "OSGI-INF" + File.separator + "blueprint" + File.separator + "components.xml";
FileResource<?> fileResource = facet.getResource(outputFilePath);
Resource<URL> xml = resourceFactory.create(getClass().getResource("/templates/my_blueprint.ftl")).reify(URLResource.class);
Template template = templateFactory.create(xml, FreemarkerTemplate.class);
Map<String, Object> templateContext = new HashMap<>();
String componentId = component.split("-")[1];
templateContext.put("componentId", componentId);
String componentClass = findComponentFQCN(component, selectedVersion);
templateContext.put("componentClass", componentClass);
UIPrompt prompt = context.getPrompt();
if (!prompt.promptBoolean("File '" + outputFilePath + "' already exists. Do you want to overwrite it?")) {
return Results.fail("File '" + outputFilePath + "' already exists.");
}
fileResource.createNewFile();
fileResource.setContents(template.process(templateContext));
break;
}
case SPRING: {
ResourcesFacet facet = getSelectedProject(context).getFacet(ResourcesFacet.class);
String outputFilePath = "META-INF" + File.separator + "spring" + File.separator + "components.xml";
FileResource<?> fileResource = facet.getResource(outputFilePath);
Resource<URL> xml = resourceFactory.create(getClass().getResource("/templates/my_spring.ftl")).reify(URLResource.class);
Template template = templateFactory.create(xml, FreemarkerTemplate.class);
Map<String, Object> templateContext = new HashMap<>();
String componentId = component.split("-")[1];
templateContext.put("componentId", componentId);
String componentClass = findComponentFQCN(component, selectedVersion);
templateContext.put("componentClass", componentClass);
UIPrompt prompt = context.getPrompt();
if (!prompt.promptBoolean("File '" + outputFilePath + "' already exists. Do you want to overwrite it?")) {
return Results.fail("File '" + outputFilePath + "' already exists.");
}
fileResource.createNewFile();
fileResource.setContents(template.process(templateContext));
break;
}
case JAVA:
break;
}
return Results.success(component + " component successfully installed!");
}
private String findComponentFQCN(String component, String selectedVersion) {
String result = null;
InputStream stream = null;
try {
File tmp = File.createTempFile("camel-dep", "jar");
URL url = new URL(String.format("https://repo1.maven.org/maven2/org/apache/camel/%s/%s/%s-%s.jar", component, selectedVersion, component, selectedVersion));
FileUtils.copyURLToFile(url, tmp);
ZipFile zipFile = new ZipFile(tmp);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().startsWith("META-INF/services/org/apache/camel/component/") && !entry.isDirectory()) {
stream = zipFile.getInputStream(entry);
Properties prop = new Properties();
prop.load(stream);
result = prop.getProperty("class");
break;
}
}
} catch (Exception e) {
throw new RuntimeException("Unable to inspect added component", e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
}
}
}
return result;
}
private void installMavenDependency(String component, String selectedVersion, Project project) {
Dependency dependency = DependencyBuilder.create().setGroupId(MVN_CAMEL_GROUPID).setVersion(selectedVersion).setArtifactId(component);
dependencyInstaller.install(project, dependency);
}
private synchronized Set<String> initializeAvailableCamelComponents() {
if (availableComponents == null) {
String camelComponentsListLocation = "https://raw.githubusercontent.com/fusesource/fuseide/master/core/plugins/org.fusesource.ide.camel.model/components/components-2.13.2.xml";
Set<String> result = new LinkedHashSet<String>();
String xmlString = null;
//xmlString = IOUtils.toString(new URL(camelComponentsListLocation));
client = factory.createClient();
try {
xmlString = client.target(camelComponentsListLocation).request(MediaType.WILDCARD_TYPE).get(String.class);
} catch (Exception e) {
throw new RuntimeException("Unable to fetch list of Camel components from: " + camelComponentsListLocation, e);
}
XML xml = new XMLDocument(xmlString);
List<String> connectors = xml.xpath("/connectors/connector/@id");
for (String id : connectors) {
result.add("camel-" + id);
}
availableComponents = Collections.unmodifiableSet(result);
}
return availableComponents;
}
private CamelSupportedTechnologyEnum detectTechnology(Project project) {
if (probeForCDI(project)) {
return CamelSupportedTechnologyEnum.JAVA;
} else if (probeForBlueprint(project)) {
return CamelSupportedTechnologyEnum.BLUEPRINT;
} else if (probeForSpring(project)) {
return CamelSupportedTechnologyEnum.SPRING;
} else {
throw new IllegalStateException("We couldn't identify Camel Project technology");
}
}
private boolean probeForSpring(Project project) {
ResourcesFacet facet = project.getFacet(ResourcesFacet.class);
FileResource<?> resource = facet.getResource("META-INF" + File.separator + "spring");
if (resource.isDirectory()) {
return true;
} else {
return false;
}
}
private boolean probeForBlueprint(Project project) {
ResourcesFacet facet = project.getFacet(ResourcesFacet.class);
FileResource<?> resource = facet.getResource("OSGI-INF");
if (resource.isDirectory()) {
return true;
} else {
return false;
}
}
private boolean probeForCDI(Project project) {
final boolean matched[] = new boolean[1];
matched[0] = false;
project.getFacet(JavaSourceFacet.class).visitJavaSources(
new JavaResourceVisitor() {
@Override
public void visit(VisitContext context, JavaResource javaResource) {
try {
JavaSource<?> javaType = javaResource.getJavaType();
if (javaType.isClass()) {
JavaClassSource source = (JavaClassSource) javaType;
if (source.hasAnnotation(org.apache.camel.cdi.ContextName.class)
|| source.hasAnnotation(org.apache.camel.cdi.Uri.class)
) {
matched[0] = true;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
});
return matched[0];
}
}
class UIVersionCompleter implements UICompleter{
AbstractCamelCommand command;
public UIVersionCompleter(AbstractCamelCommand command){
this.command = command;
}
@Override
public Iterable getCompletionProposals(UIContext context, InputComponent input, String value) {
List<String> result = new ArrayList<>(1);
result.add(command.getCamelCoreVersion());
return result;
}
}