Created
March 2, 2017 10:18
-
-
Save sebastienblanc/91671d76bb187fcf3168e355db7a2b97 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright 2005-2015 Red Hat, Inc. | |
* <p/> | |
* 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 | |
* <p/> | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* <p/> | |
* 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.devops.springboot; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import javax.inject.Inject; | |
import io.fabric8.forge.devops.AbstractDevOpsCommand; | |
import io.fabric8.forge.devops.dto.SpringBootDependencyDTO; | |
import okhttp3.OkHttpClient; | |
import okhttp3.Request; | |
import okhttp3.Response; | |
import org.jboss.forge.addon.convert.Converter; | |
import org.jboss.forge.addon.dependencies.builder.DependencyBuilder; | |
import org.jboss.forge.addon.projects.Project; | |
import org.jboss.forge.addon.projects.dependencies.DependencyInstaller; | |
import org.jboss.forge.addon.projects.facets.MetadataFacet; | |
import org.jboss.forge.addon.resource.DirectoryResource; | |
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.UINavigationContext; | |
import org.jboss.forge.addon.ui.input.UIInput; | |
import org.jboss.forge.addon.ui.input.UISelectMany; | |
import org.jboss.forge.addon.ui.input.UISelectOne; | |
import org.jboss.forge.addon.ui.input.ValueChangeListener; | |
import org.jboss.forge.addon.ui.input.events.ValueChangeEvent; | |
import org.jboss.forge.addon.ui.metadata.UICommandMetadata; | |
import org.jboss.forge.addon.ui.metadata.WithAttributes; | |
import org.jboss.forge.addon.ui.result.NavigationResult; | |
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.Commands; | |
import org.jboss.forge.addon.ui.util.Metadata; | |
import org.jboss.forge.addon.ui.wizard.UIWizard; | |
import org.jboss.forge.furnace.util.Strings; | |
import org.json.JSONArray; | |
import org.json.JSONException; | |
import org.json.JSONObject; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.yaml.snakeyaml.Yaml; | |
import static io.fabric8.forge.devops.springboot.IOHelper.close; | |
import static io.fabric8.forge.devops.springboot.IOHelper.copyAndCloseInput; | |
import static io.fabric8.forge.devops.springboot.OkHttpClientHelper.createOkHttpClient; | |
import static io.fabric8.forge.devops.springboot.UnzipHelper.unzip; | |
import static io.fabric8.utils.Files.recursiveDelete; | |
public class SpringBootNewProjectCommand extends AbstractDevOpsCommand implements UIWizard { | |
private static final transient Logger LOG = LoggerFactory.getLogger(SpringBootNewProjectCommand.class); | |
// lets use a different category for this command | |
private static final String CATEGORY = "Spring Boot"; | |
// lets use 1.3.x which currently fabric8 works best with | |
private static final String SPRING_BOOT_DEFAULT_VERSION = "1.4.2"; | |
private static final String[] SPRING_BOOT_VERSIONS = new String[]{"1.3.8", "1.4.2"}; | |
private static final String STARTER_DEFAULT_URL = "http://localhost:8080"; | |
// fabric8 only dependencies which we should not pass on to start.spring.io | |
private static final String[] fabric8Deps = new String[]{"spring-cloud-kubernetes", "kubeflix-ribbon-discovery", | |
"kubeflix-turbine-discovery", "kubeflix-turbine-server", "camel-zipkin-starter"}; | |
@Inject | |
@WithAttributes(label = "Spring Boot Version", required = true, description = "Spring Boot Version to use") | |
private UISelectOne<String> springBootVersion; | |
@Inject | |
@WithAttributes(defaultValue=STARTER_DEFAULT_URL,label = "Spring Initializr URL", required = true, description = "Spring Initializr URL") | |
private UIInput<String> springBootInitializrURL; | |
private List<SpringBootDependencyDTO> choices; | |
@Inject | |
@WithAttributes(label = "Dependencies", required = true, description = "Add Spring Boot Starters and dependencies to your application") | |
private UISelectMany<SpringBootDependencyDTO> dependencies; | |
@Inject | |
private DependencyInstaller dependencyInstaller; | |
@Override | |
public NavigationResult next(UINavigationContext context) throws Exception { | |
return null; | |
} | |
@Override | |
public void initializeUI(UIBuilder builder) throws Exception { | |
springBootVersion.setValueChoices(Arrays.asList(SPRING_BOOT_VERSIONS)); | |
springBootVersion.setDefaultValue(SPRING_BOOT_DEFAULT_VERSION); | |
springBootInitializrURL.setDefaultValue(STARTER_DEFAULT_URL); | |
springBootInitializrURL.addValueChangeListener(new ValueChangeListener() { | |
@Override | |
public void valueChanged(ValueChangeEvent arg0) { | |
try { | |
if(arg0.getNewValue().toString().length() > 5){ | |
choices = initDependencies(); | |
dependencies.setValueChoices(choices); | |
} | |
} catch (IOException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
}); | |
try { | |
choices = initDependencies(); | |
} catch (Exception e) { | |
throw new IllegalStateException("Error loading dependencies from spring-boot-application.yaml due: " + e.getMessage(), e); | |
} | |
dependencies.setValueChoices(choices); | |
if (builder.getUIContext().getProvider().isGUI()) { | |
dependencies.setItemLabelConverter(SpringBootDependencyDTO::getGroupAndName); | |
} else { | |
// if in CLI mode then use shorter names so they are tab friendly in the shell | |
dependencies.setItemLabelConverter(dto -> Commands.shellifyCommandName(dto.getName())); | |
} | |
dependencies.setValueConverter(s -> { | |
for (SpringBootDependencyDTO dto : choices) { | |
if (dto.getId().equals(s)) { | |
return dto; | |
} | |
} | |
return null; | |
}); | |
builder.add(springBootVersion).add(springBootInitializrURL).add(dependencies); | |
} | |
private List<SpringBootDependencyDTO> initDependencies() throws IOException { | |
List<SpringBootDependencyDTO> list = new ArrayList<>(); | |
Map data; | |
// use http client to call start.spring.io that creates the project | |
OkHttpClient client = createOkHttpClient(); | |
Request request = new Request.Builder().url(springBootInitializrURL.getValue()).build(); | |
Response response = client.newCall(request).execute(); | |
data = jsonToMap(response.body().string()); | |
Map dependencies = (Map) data.get("dependencies"); | |
List deps = (List) dependencies.get("values"); | |
for (Object dep : deps) { | |
Map group = (Map) dep; | |
String groupName = (String) group.get("name"); | |
List content = (List) group.get("values"); | |
for (Object row : content) { | |
Map item = (Map) row; | |
String id = (String) item.get("id"); | |
String name = (String) item.get("name"); | |
String description = (String) item.get("description"); | |
list.add(new SpringBootDependencyDTO(groupName, id, name, description)); | |
// are we at apache camel, then inject other Camel modules that are not in the spring-boot-application yet | |
if ("camel".equals(id)) { | |
SpringBootDependencyDTO dto = new SpringBootDependencyDTO(groupName, "camel-zipkin-starter", "Apache Camel Zipkin", "Distributed tracing with an existing Zipkin installation with Apache Camel."); | |
String version = SpringBootVersionHelper.getVersion("camel.version"); | |
dto.setMavenCoord("org.apache.camel", "camel-zipkin", version); | |
list.add(dto); | |
} | |
} | |
} | |
// and then add the fabric8 group | |
String version = SpringBootVersionHelper.getVersion("fabric8.spring.cloud.kubernetes.version"); | |
SpringBootDependencyDTO dto = new SpringBootDependencyDTO("Fabric8", "spring-cloud-kubernetes", "Spring Cloud Kubernetes", "Kubernetes integration with Spring Cloud"); | |
dto.setMavenCoord("io.fabric8", "spring-cloud-starter-kubernetes-all", version); | |
list.add(dto); | |
return list; | |
} | |
@Override | |
protected boolean isProjectRequired() { | |
return false; | |
} | |
@Override | |
public UICommandMetadata getMetadata(UIContext context) { | |
return Metadata.from(super.getMetadata(context), getClass()) | |
.category(Categories.create(CATEGORY)) | |
.name(CATEGORY + ": New Project") | |
.description("Create a new Spring Boot project"); | |
} | |
@Override | |
public Result execute(UIExecutionContext context) throws Exception { | |
UIContext uiContext = context.getUIContext(); | |
Project project = (Project) uiContext.getAttributeMap().get(Project.class); | |
if (project == null) { | |
project = getSelectedProject(context.getUIContext()); | |
} | |
MetadataFacet metadataFacet = project.getFacet(MetadataFacet.class); | |
String projectName = metadataFacet.getProjectName(); | |
String groupId = metadataFacet.getProjectGroupName(); | |
String version = metadataFacet.getProjectVersion(); | |
File folder = project.getRoot().reify(DirectoryResource.class).getUnderlyingResourceObject(); | |
Map<String, SpringBootDependencyDTO> selectedDTOs = new HashMap<>(); | |
int[] selected = dependencies.getSelectedIndexes(); | |
CollectionStringBuffer csbSpringBoot = new CollectionStringBuffer(","); | |
CollectionStringBuffer csbFabric8 = new CollectionStringBuffer(","); | |
for (int val : selected) { | |
SpringBootDependencyDTO dto = choices.get(val); | |
if (isFabric8Dependency(dto.getId())) { | |
csbFabric8.append(dto.getId()); | |
} else { | |
csbSpringBoot.append(dto.getId()); | |
} | |
selectedDTOs.put(dto.getId(), dto); | |
} | |
String springBootDeps = csbSpringBoot.toString(); | |
String fabric8Deps = csbFabric8.toString(); | |
// boot version need the RELEASE suffix | |
String bootVersion = springBootVersion.getValue() + ".RELEASE"; | |
String url = String.format("%s?bootVersion=%s&groupId=%s&artifactId=%s&version=%s&packageName=%s&dependencies=%s", | |
springBootInitializrURL.getValue()+"/starter.zip", bootVersion, groupId, projectName, version, groupId, springBootDeps); | |
System.out.println("About to query url: " + url); | |
// use http client to call start.spring.io that creates the project | |
OkHttpClient client = createOkHttpClient(); | |
Request request = new Request.Builder().url(url).build(); | |
Response response = client.newCall(request).execute(); | |
InputStream is = response.body().byteStream(); | |
// some archetypes might not use maven or use the maven source layout so lets remove | |
// the pom.xml and src folder if its already been pre-created | |
// as these will be created if necessary via the archetype jar's contents | |
File pom = new File(folder, "pom.xml"); | |
if (pom.isFile() && pom.exists()) { | |
pom.delete(); | |
} | |
File src = new File(folder, "src"); | |
if (src.isDirectory() && src.exists()) { | |
recursiveDelete(src); | |
} | |
File name = new File(folder, projectName + ".zip"); | |
if (name.exists()) { | |
name.delete(); | |
} | |
FileOutputStream fos = new FileOutputStream(name, false); | |
copyAndCloseInput(is, fos); | |
close(fos); | |
// unzip the download from spring starter | |
unzip(name, folder); | |
LOG.info("Unzipped file to folder: {}", folder.getAbsolutePath()); | |
// and delete the zip file | |
name.delete(); | |
if (!Strings.isEmpty(fabric8Deps)) { | |
addFabric8DependenciesToPom(project, fabric8Deps, selectedDTOs); | |
} | |
// are there any fabric8 dependencies to add afterwards? | |
return Results.success("Created new Spring Boot project in directory: " + folder.getName()); | |
} | |
private void addFabric8DependenciesToPom(Project project, String fabric8Deps, Map<String, SpringBootDependencyDTO> selectedDTOs) { | |
String[] deps = fabric8Deps.split(","); | |
for (String dep : deps) { | |
SpringBootDependencyDTO dto = selectedDTOs.get(dep); | |
if (dto != null) { | |
DependencyBuilder dp = DependencyBuilder.create().setGroupId(dto.getGroupId()).setArtifactId(dto.getArtifactId()).setVersion(dto.getVersion()); | |
dependencyInstaller.install(project, dp); | |
} | |
} | |
} | |
private boolean isFabric8Dependency(String depId) { | |
for (String id : fabric8Deps) { | |
if (depId.equals(id)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
private Map jsonToMap(String t) throws JSONException { | |
HashMap<String, String> map = new HashMap<String, String>(); | |
JSONObject jObject = new JSONObject(t); | |
return toMap(jObject); | |
} | |
private Map<String, Object> toMap(JSONObject object) throws JSONException { | |
Map<String, Object> map = new HashMap<String, Object>(); | |
Iterator<String> keysItr = object.keys(); | |
while(keysItr.hasNext()) { | |
String key = keysItr.next(); | |
Object value = object.get(key); | |
if(value instanceof JSONArray) { | |
value = toList((JSONArray) value); | |
} | |
else if(value instanceof JSONObject) { | |
value = toMap((JSONObject) value); | |
} | |
map.put(key, value); | |
} | |
return map; | |
} | |
private List<Object> toList(JSONArray array) throws JSONException { | |
List<Object> list = new ArrayList<Object>(); | |
for(int i = 0; i < array.length(); i++) { | |
Object value = array.get(i); | |
if(value instanceof JSONArray) { | |
value = toList((JSONArray) value); | |
} | |
else if(value instanceof JSONObject) { | |
value = toMap((JSONObject) value); | |
} | |
list.add(value); | |
} | |
return list; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment