camel-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lburgazz...@apache.org
Subject [camel-k] 01/06: Refactoring traits package
Date Mon, 05 Nov 2018 19:46:30 GMT
This is an automated email from the ASF dual-hosted git repository.

lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit bacb33c132bd09ea0154633894c942a51d27e927
Author: nferraro <ni.ferraro@gmail.com>
AuthorDate: Fri Oct 26 00:04:53 2018 +0200

    Refactoring traits package
---
 docs/traits.adoc                                   |   6 +-
 pkg/client/cmd/completion_bash.go                  |   2 +-
 pkg/client/cmd/run.go                              |   3 +-
 pkg/{discover => metadata}/dependencies.go         |  62 ++----
 pkg/{discover => metadata}/doc.go                  |   5 +-
 .../language.go => metadata/languages.go}          |   7 +-
 .../languages_test.go => metadata/metadata.go}     |  30 ++-
 .../metadata_dependencies_test.go}                 |  22 +--
 .../metadata_languages_test.go}                    |  10 +-
 pkg/metadata/metadata_uri_test.go                  | 215 +++++++++++++++++++++
 pkg/{discover/doc.go => metadata/types.go}         |  18 +-
 pkg/metadata/uris.go                               | 100 ++++++++++
 pkg/stub/action/integration/initialize.go          |  11 +-
 pkg/trait/catalog.go                               | 139 +++++++++----
 pkg/trait/{base.go => deployment.go}               |  18 +-
 pkg/trait/owner.go                                 |  12 +-
 pkg/trait/route.go                                 |  38 ++--
 pkg/trait/service.go                               |  36 ++--
 pkg/trait/trait.go                                 |  13 +-
 pkg/trait/trait_test.go                            |  56 +++---
 pkg/trait/types.go                                 | 113 +++++------
 pkg/trait/util.go                                  |  31 ---
 22 files changed, 627 insertions(+), 320 deletions(-)

diff --git a/docs/traits.adoc b/docs/traits.adoc
index c5ada79..efe2aa1 100644
--- a/docs/traits.adoc
+++ b/docs/traits.adoc
@@ -23,6 +23,10 @@ The flag `--trait` can be also abbreviated with `-t`.
 The `enabled` property is available on all traits and can be used to enable/disable them. All traits have their own
 internal logic to determine if they need to be enabled when the user does not activate them explicitly.
 
+All traits share also a `auto` property that can be used to enable/disable auto-configuration of the trait based on the
+environment. The auto-configuration mechanism is able to enable/disable the trait when the `enabled` property is not explicitly
+set by the user and also change the trait configuration. The `auto` property is enabled by default.
+
 NOTE: Some traits are applicable only to specific platforms (see "profiles" in the table).
 
 A trait may have additional properties that can be configured by the end user.
@@ -83,6 +87,6 @@ There are also platform traits that **normally should not be configured** by the
 [options="header",cols="m,,"]
 |=======================
 | Trait      | Profiles 				| Description
-| base		 | Kubernetes, OpenShift	| Creates the basic Kubernetes resource needed for running the integration.
+| deployment | Kubernetes, OpenShift	| Creates the basic Kubernetes resource needed for running the integration.
 | owner      | Kubernetes, OpenShift	| Makes sure that every resource created by the traits belongs to the integration custom resource (so they are deleted when the integration is deleted).
 |=======================
diff --git a/pkg/client/cmd/completion_bash.go b/pkg/client/cmd/completion_bash.go
index df76a1e..ab390fe 100644
--- a/pkg/client/cmd/completion_bash.go
+++ b/pkg/client/cmd/completion_bash.go
@@ -69,7 +69,7 @@ __kamel_dependency_type() {
 }
 
 __kamel_traits() {
-    local type_list="` + strings.Join(trait.ComputeTraitsProperties(), " ") + `"
+    local type_list="` + strings.Join(trait.NewCatalog().ComputeTraitsProperties(), " ") + `"
     COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") )
     compopt -o nospace
 }
diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go
index 0778b07..3c59c84 100644
--- a/pkg/client/cmd/run.go
+++ b/pkg/client/cmd/run.go
@@ -127,7 +127,8 @@ func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
 }
 
 func (o *runCmdOptions) run(cmd *cobra.Command, args []string) error {
-	tp := trait.ComputeTraitsProperties()
+	catalog := trait.NewCatalog()
+	tp := catalog.ComputeTraitsProperties()
 	for _, t := range o.Traits {
 		kv := strings.SplitN(t, "=", 2)
 
diff --git a/pkg/discover/dependencies.go b/pkg/metadata/dependencies.go
similarity index 50%
rename from pkg/discover/dependencies.go
rename to pkg/metadata/dependencies.go
index 8e402b2..d207228 100644
--- a/pkg/discover/dependencies.go
+++ b/pkg/metadata/dependencies.go
@@ -15,10 +15,9 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package discover
+package metadata
 
 import (
-	"regexp"
 	"sort"
 	"strings"
 
@@ -26,23 +25,14 @@ import (
 	"github.com/apache/camel-k/pkg/util/camel"
 )
 
-var (
-	singleQuotedURI *regexp.Regexp
-	doubleQuotedURI *regexp.Regexp
-)
-
-func init() {
-	singleQuotedURI = regexp.MustCompile("'([a-z0-9-]+):[^']+'")
-	doubleQuotedURI = regexp.MustCompile("\"([a-z0-9-]+):[^\"]+\"")
-}
-
-// Dependencies returns a list of dependencies required by the given source code
-func Dependencies(source v1alpha1.SourceSpec) []string {
+// discoverDependencies returns a list of dependencies required by the given source code
+func discoverDependencies(source v1alpha1.SourceSpec, fromURIs []string, toURIs []string) []string {
 	candidateMap := make(map[string]bool)
-	regexps := getRegexpsForLanguage(source.Language)
-	subMatches := findAllStringSubmatch(source.Content, regexps...)
-	for _, uriPrefix := range subMatches {
-		candidateComp := decodeComponent(uriPrefix)
+	uris := make([]string, 0, len(fromURIs)+len(toURIs))
+	uris = append(uris, fromURIs...)
+	uris = append(uris, toURIs...)
+	for _, uri := range uris {
+		candidateComp := decodeComponent(uri)
 		if candidateComp != "" {
 			candidateMap[candidateComp] = true
 		}
@@ -56,38 +46,12 @@ func Dependencies(source v1alpha1.SourceSpec) []string {
 	return candidateComponents
 }
 
-func getRegexpsForLanguage(language v1alpha1.Language) []*regexp.Regexp {
-	switch language {
-	case v1alpha1.LanguageJavaSource:
-		return []*regexp.Regexp{doubleQuotedURI}
-	case v1alpha1.LanguageXML:
-		return []*regexp.Regexp{doubleQuotedURI}
-	case v1alpha1.LanguageGroovy:
-		return []*regexp.Regexp{singleQuotedURI, doubleQuotedURI}
-	case v1alpha1.LanguageJavaScript:
-		return []*regexp.Regexp{singleQuotedURI, doubleQuotedURI}
-	case v1alpha1.LanguageKotlin:
-		return []*regexp.Regexp{doubleQuotedURI}
-	}
-	return []*regexp.Regexp{}
-}
-
-func findAllStringSubmatch(data string, regexps ...*regexp.Regexp) []string {
-	candidates := make([]string, 0)
-	for _, reg := range regexps {
-		hits := reg.FindAllStringSubmatch(data, -1)
-		for _, hit := range hits {
-			if hit != nil && len(hit) > 1 {
-				for _, match := range hit[1:] {
-					candidates = append(candidates, match)
-				}
-			}
-		}
+func decodeComponent(uri string) string {
+	uriSplit := strings.SplitN(uri, ":", 2)
+	if len(uriSplit) < 2 {
+		return ""
 	}
-	return candidates
-}
-
-func decodeComponent(uriStart string) string {
+	uriStart := uriSplit[0]
 	if component := camel.Runtime.GetArtifactByScheme(uriStart); component != nil {
 		artifactID := component.ArtifactID
 		if strings.HasPrefix(artifactID, "camel-") {
diff --git a/pkg/discover/doc.go b/pkg/metadata/doc.go
similarity index 86%
copy from pkg/discover/doc.go
copy to pkg/metadata/doc.go
index 51cc065..e1b5958 100644
--- a/pkg/discover/doc.go
+++ b/pkg/metadata/doc.go
@@ -15,6 +15,5 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// Package discover contains functions for extracting
-// information from user code before compilation
-package discover
+// Package metadata contains tools to discover metadata from Camel routes
+package metadata
diff --git a/pkg/discover/language.go b/pkg/metadata/languages.go
similarity index 84%
rename from pkg/discover/language.go
rename to pkg/metadata/languages.go
index cafb6c3..2d0d2da 100644
--- a/pkg/discover/language.go
+++ b/pkg/metadata/languages.go
@@ -15,8 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// Package discover contains functions for analyzing user code
-package discover
+package metadata
 
 import (
 	"strings"
@@ -24,8 +23,8 @@ import (
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 )
 
-// Language discovers the code language from file extension if not set
-func Language(source v1alpha1.SourceSpec) v1alpha1.Language {
+// discoverLanguage discovers the code language from file extension if not set
+func discoverLanguage(source v1alpha1.SourceSpec) v1alpha1.Language {
 	if source.Language != "" {
 		return source.Language
 	}
diff --git a/pkg/discover/languages_test.go b/pkg/metadata/metadata.go
similarity index 63%
copy from pkg/discover/languages_test.go
copy to pkg/metadata/metadata.go
index bbe0c45..46a7aef 100644
--- a/pkg/discover/languages_test.go
+++ b/pkg/metadata/metadata.go
@@ -15,28 +15,22 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package discover
+package metadata
 
 import (
-	"testing"
-
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-	"github.com/stretchr/testify/assert"
 )
 
-func TestLanguageJavaSource(t *testing.T) {
-	code := v1alpha1.SourceSpec{
-		Name: "Request.java",
-	}
-	language := Language(code)
-	assert.Equal(t, v1alpha1.LanguageJavaSource, language)
-}
-
-func TestLanguageAlreadySet(t *testing.T) {
-	code := v1alpha1.SourceSpec{
-		Name:     "Request.java",
-		Language: v1alpha1.LanguageJavaScript,
+// Extract returns metadata information from the source code
+func Extract(source v1alpha1.SourceSpec) IntegrationMetadata {
+	language := discoverLanguage(source)
+	fromURIs := discoverFromURIs(source, language)
+	toURIs := discoverToURIs(source, language)
+	dependencies := discoverDependencies(source, fromURIs, toURIs)
+	return IntegrationMetadata{
+		Language:     language,
+		FromURIs:     fromURIs,
+		ToURIs:       toURIs,
+		Dependencies: dependencies,
 	}
-	language := Language(code)
-	assert.Equal(t, v1alpha1.LanguageJavaScript, language)
 }
diff --git a/pkg/discover/dependencies_test.go b/pkg/metadata/metadata_dependencies_test.go
similarity index 90%
rename from pkg/discover/dependencies_test.go
rename to pkg/metadata/metadata_dependencies_test.go
index a7934ef..8eea487 100644
--- a/pkg/discover/dependencies_test.go
+++ b/pkg/metadata/metadata_dependencies_test.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package discover
+package metadata
 
 import (
 	"testing"
@@ -34,9 +34,9 @@ func TestDependenciesJavaSource(t *testing.T) {
 			from("ine:xistent").to("amqp:queue");
 		`,
 	}
-	dependencies := Dependencies(code)
+	meta := Extract(code)
 	// assert all dependencies are found and sorted (removing duplicates)
-	assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, dependencies)
+	assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, meta.Dependencies)
 }
 
 func TestDependenciesJavaClass(t *testing.T) {
@@ -49,8 +49,8 @@ func TestDependenciesJavaClass(t *testing.T) {
 			from("ine:xistent").to("amqp:queue");
 		`,
 	}
-	dependencies := Dependencies(code)
-	assert.Empty(t, dependencies)
+	meta := Extract(code)
+	assert.Empty(t, meta.Dependencies)
 }
 
 func TestDependenciesJavaScript(t *testing.T) {
@@ -64,9 +64,9 @@ func TestDependenciesJavaScript(t *testing.T) {
 			'"'
 		`,
 	}
-	dependencies := Dependencies(code)
+	meta := Extract(code)
 	// assert all dependencies are found and sorted (removing duplicates)
-	assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, dependencies)
+	assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, meta.Dependencies)
 }
 
 func TestDependenciesGroovy(t *testing.T) {
@@ -80,9 +80,9 @@ func TestDependenciesGroovy(t *testing.T) {
 			'"'
 		`,
 	}
-	dependencies := Dependencies(code)
+	meta := Extract(code)
 	// assert all dependencies are found and sorted (removing duplicates)
-	assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, dependencies)
+	assert.Equal(t, []string{"camel:amqp", "camel:core", "camel:telegram"}, meta.Dependencies)
 }
 
 func TestDependencies(t *testing.T) {
@@ -95,7 +95,7 @@ func TestDependencies(t *testing.T) {
 			from("twitter-timeline:test").to("mock:end");
 		`,
 	}
-	dependencies := Dependencies(code)
+	meta := Extract(code)
 	// assert all dependencies are found and sorted (removing duplicates)
-	assert.Equal(t, []string{"camel:core", "camel:http4", "camel:twitter"}, dependencies)
+	assert.Equal(t, []string{"camel:core", "camel:http4", "camel:twitter"}, meta.Dependencies)
 }
diff --git a/pkg/discover/languages_test.go b/pkg/metadata/metadata_languages_test.go
similarity index 85%
rename from pkg/discover/languages_test.go
rename to pkg/metadata/metadata_languages_test.go
index bbe0c45..5382d38 100644
--- a/pkg/discover/languages_test.go
+++ b/pkg/metadata/metadata_languages_test.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package discover
+package metadata
 
 import (
 	"testing"
@@ -28,8 +28,8 @@ func TestLanguageJavaSource(t *testing.T) {
 	code := v1alpha1.SourceSpec{
 		Name: "Request.java",
 	}
-	language := Language(code)
-	assert.Equal(t, v1alpha1.LanguageJavaSource, language)
+	meta := Extract(code)
+	assert.Equal(t, v1alpha1.LanguageJavaSource, meta.Language)
 }
 
 func TestLanguageAlreadySet(t *testing.T) {
@@ -37,6 +37,6 @@ func TestLanguageAlreadySet(t *testing.T) {
 		Name:     "Request.java",
 		Language: v1alpha1.LanguageJavaScript,
 	}
-	language := Language(code)
-	assert.Equal(t, v1alpha1.LanguageJavaScript, language)
+	meta := Extract(code)
+	assert.Equal(t, v1alpha1.LanguageJavaScript, meta.Language)
 }
diff --git a/pkg/metadata/metadata_uri_test.go b/pkg/metadata/metadata_uri_test.go
new file mode 100644
index 0000000..84da92a
--- /dev/null
+++ b/pkg/metadata/metadata_uri_test.go
@@ -0,0 +1,215 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF 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 metadata
+
+import (
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestJava1(t *testing.T) {
+	source := v1alpha1.SourceSpec{
+		Name:     "test",
+		Language: v1alpha1.LanguageJavaSource,
+		Content: `
+			import org.apache.camel.builder.RouteBuilder;
+
+			public class Sample extends RouteBuilder {
+  				@Override
+  				public void configure() throws Exception {
+		  			from("timer:tick")
+		    			.setBody(constant("-\n             r\n             o\n             c\nHello! Camel K\n             s\n             !\n"))
+							.to("log:info?skipBodyLineSeparator=false");
+  				}
+			}
+		`,
+	}
+
+	metadata := Extract(source)
+	assert.Contains(t, metadata.FromURIs, "timer:tick")
+	assert.Len(t, metadata.FromURIs, 1)
+	assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false")
+	assert.Len(t, metadata.ToURIs, 1)
+}
+
+func TestJava2(t *testing.T) {
+	source := v1alpha1.SourceSpec{
+		Name:     "test",
+		Language: v1alpha1.LanguageJavaSource,
+		Content: `
+			import org.apache.camel.builder.RouteBuilder;
+
+			public class Sample extends RouteBuilder {
+  				@Override
+  				public void configure() throws Exception {
+		  			from("timer:tick")
+		    			.setBody(constant("!\n"))
+							.to           (
+
+											"log:info?skipBodyLineSeparator=false"
+
+							               )
+							.toD("uri:2")
+							.toF("uri:%s", "3");
+  				}
+			}
+		`,
+	}
+
+	metadata := Extract(source)
+	assert.Contains(t, metadata.FromURIs, "timer:tick")
+	assert.Len(t, metadata.FromURIs, 1)
+	assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false")
+	assert.Contains(t, metadata.ToURIs, "uri:2")
+	assert.Contains(t, metadata.ToURIs, "uri:%s") // resolution not supported yet
+	assert.Len(t, metadata.ToURIs, 3)
+}
+
+func TestGroovy1(t *testing.T) {
+	source := v1alpha1.SourceSpec{
+		Name:     "test",
+		Language: v1alpha1.LanguageGroovy,
+		Content: `
+			
+		  	from( "timer:tick")
+		    	.setBody().constant("aa")
+				.to   ('log:info?skipBodyLineSeparator=false').to(
+											'http://url' )
+  			
+			from("uri:2")
+		    	.setBody().constant("aa")
+				.to('uri:3')
+		`,
+	}
+
+	metadata := Extract(source)
+	assert.Contains(t, metadata.FromURIs, "timer:tick")
+	assert.Contains(t, metadata.FromURIs, "uri:2")
+	assert.Len(t, metadata.FromURIs, 2)
+	assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false")
+	assert.Contains(t, metadata.ToURIs, "http://url")
+	assert.Contains(t, metadata.ToURIs, "uri:3")
+	assert.Len(t, metadata.ToURIs, 3)
+}
+
+func TestGroovy2(t *testing.T) {
+	source := v1alpha1.SourceSpec{
+		Name:     "test",
+		Language: v1alpha1.LanguageGroovy,
+		Content: `
+			
+			rest().get("/")
+				.to   ('log:info?skipBodyLineSeparator=false').to( 'http://url' )
+						.toD('dyn:1')
+						.tony('thisisnot:anuri')
+						.toD( "dyn:2")
+						.toF( "f:%s", "2")
+		`,
+	}
+
+	metadata := Extract(source)
+	assert.Empty(t, metadata.FromURIs)
+	assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false")
+	assert.Contains(t, metadata.ToURIs, "http://url")
+	assert.Contains(t, metadata.ToURIs, "dyn:1")
+	assert.Contains(t, metadata.ToURIs, "dyn:2")
+	assert.Contains(t, metadata.ToURIs, "f:%s") // resolution not supported yet
+	assert.Len(t, metadata.ToURIs, 5)
+}
+
+func TestXml1(t *testing.T) {
+	source := v1alpha1.SourceSpec{
+		Name:     "test",
+		Language: v1alpha1.LanguageXML,
+		Content: `
+			<routes>
+			    <route id="hello">
+        			<from uri="timer:hello?period=3s"/>
+        			<setBody>
+            			<constant>Hello World!!!</constant>
+        			</setBody>
+        			<to uri="log:info"/>
+					<to uri="log:info2"/>
+					<toD uri="log:info3"/>
+    			</route>
+			</routes>
+		`,
+	}
+
+	metadata := Extract(source)
+	assert.Contains(t, metadata.FromURIs, "timer:hello?period=3s")
+	assert.Len(t, metadata.FromURIs, 1)
+	assert.Contains(t, metadata.ToURIs, "log:info")
+	assert.Contains(t, metadata.ToURIs, "log:info2")
+	assert.Contains(t, metadata.ToURIs, "log:info3")
+	assert.Len(t, metadata.ToURIs, 3)
+}
+
+func TestKotlin1(t *testing.T) {
+	source := v1alpha1.SourceSpec{
+		Name:     "test",
+		Language: v1alpha1.LanguageKotlin,
+		Content: `
+			
+		  	from( "timer:tick")
+		    	.setBody().constant("aa")
+				.to   ("log:info?skipBodyLineSeparator=false").to(
+											"http://url" )
+  			
+			from("uri:2")
+		    	.setBody().constant("aa")
+				.to("uri:3")
+				.toD("uri:4")
+				.toF("uri:%s", 5)
+		`,
+	}
+
+	metadata := Extract(source)
+	assert.Contains(t, metadata.FromURIs, "timer:tick")
+	assert.Contains(t, metadata.FromURIs, "uri:2")
+	assert.Len(t, metadata.FromURIs, 2)
+	assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false")
+	assert.Contains(t, metadata.ToURIs, "http://url")
+	assert.Contains(t, metadata.ToURIs, "uri:3")
+	assert.Contains(t, metadata.ToURIs, "uri:4")
+	assert.Contains(t, metadata.ToURIs, "uri:%s") // resolution not supported yet
+	assert.Len(t, metadata.ToURIs, 5)
+}
+
+func TestJavascript1(t *testing.T) {
+	source := v1alpha1.SourceSpec{
+		Name:     "test",
+		Language: v1alpha1.LanguageJavaScript,
+		Content: `
+			
+			rest().get("/")
+				.to   ('log:info?skipBodyLineSeparator=false').to( 'http://url' )
+				.toD("uri:2")
+				.toF("uri:%s", "3") 
+		`,
+	}
+
+	metadata := Extract(source)
+	assert.Empty(t, metadata.FromURIs)
+	assert.Contains(t, metadata.ToURIs, "log:info?skipBodyLineSeparator=false")
+	assert.Contains(t, metadata.ToURIs, "http://url")
+	assert.Contains(t, metadata.ToURIs, "uri:2")
+	assert.Contains(t, metadata.ToURIs, "uri:%s") // resolution not supported yet
+	assert.Len(t, metadata.ToURIs, 4)
+}
diff --git a/pkg/discover/doc.go b/pkg/metadata/types.go
similarity index 61%
rename from pkg/discover/doc.go
rename to pkg/metadata/types.go
index 51cc065..74ac309 100644
--- a/pkg/discover/doc.go
+++ b/pkg/metadata/types.go
@@ -15,6 +15,18 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// Package discover contains functions for extracting
-// information from user code before compilation
-package discover
+package metadata
+
+import "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+
+// IntegrationMetadata contains aggregate metadata about all Camel routes in a integrations
+type IntegrationMetadata struct {
+	// All starting URIs of defined routes
+	FromURIs []string
+	// All end URIs of defined routes
+	ToURIs   []string
+	// All inferred dependencies required to run the integration
+	Dependencies []string
+	// The language in which the integration is written
+	Language v1alpha1.Language
+}
diff --git a/pkg/metadata/uris.go b/pkg/metadata/uris.go
new file mode 100644
index 0000000..e9c17ab
--- /dev/null
+++ b/pkg/metadata/uris.go
@@ -0,0 +1,100 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF 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 metadata
+
+import (
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"regexp"
+)
+
+var (
+	singleQuotedFrom = regexp.MustCompile("from\\s*\\(\\s*'([a-z0-9-]+:[^']+)'\\s*\\)")
+	doubleQuotedFrom = regexp.MustCompile("from\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"\\s*\\)")
+	singleQuotedTo   = regexp.MustCompile("\\.to\\s*\\(\\s*'([a-z0-9-]+:[^']+)'\\s*\\)")
+	singleQuotedToD  = regexp.MustCompile("\\.toD\\s*\\(\\s*'([a-z0-9-]+:[^']+)'\\s*\\)")
+	singleQuotedToF  = regexp.MustCompile("\\.toF\\s*\\(\\s*'([a-z0-9-]+:[^']+)'[^)]*\\)")
+	doubleQuotedTo   = regexp.MustCompile("\\.to\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"\\s*\\)")
+	doubleQuotedToD  = regexp.MustCompile("\\.toD\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"\\s*\\)")
+	doubleQuotedToF  = regexp.MustCompile("\\.toF\\s*\\(\\s*\"([a-z0-9-]+:[^\"]+)\"[^)]*\\)")
+	xmlTagFrom       = regexp.MustCompile("<\\s*from\\s+[^>]*uri\\s*=\\s*\"([a-z0-9-]+:[^\"]+)\"[^>]*>")
+	xmlTagTo         = regexp.MustCompile("<\\s*to\\s+[^>]*uri\\s*=\\s*\"([a-z0-9-]+:[^\"]+)\"[^>]*>")
+	xmlTagToD        = regexp.MustCompile("<\\s*toD\\s+[^>]*uri\\s*=\\s*\"([a-z0-9-]+:[^\"]+)\"[^>]*>")
+)
+
+// discoverFromURIs returns all uris used in a from clause
+func discoverFromURIs(source v1alpha1.SourceSpec, language v1alpha1.Language) []string {
+	fromRegexps := getFromRegexpsForLanguage(language)
+	return findAllDistinctStringSubmatch(source.Content, fromRegexps...)
+}
+
+// discoverToURIs returns all uris used in a to clause
+func discoverToURIs(source v1alpha1.SourceSpec, language v1alpha1.Language) []string {
+	toRegexps := getToRegexpsForLanguage(language)
+	return findAllDistinctStringSubmatch(source.Content, toRegexps...)
+}
+
+func getFromRegexpsForLanguage(language v1alpha1.Language) []*regexp.Regexp {
+	switch language {
+	case v1alpha1.LanguageJavaSource:
+		return []*regexp.Regexp{doubleQuotedFrom}
+	case v1alpha1.LanguageXML:
+		return []*regexp.Regexp{xmlTagFrom}
+	case v1alpha1.LanguageGroovy:
+		return []*regexp.Regexp{singleQuotedFrom, doubleQuotedFrom}
+	case v1alpha1.LanguageJavaScript:
+		return []*regexp.Regexp{singleQuotedFrom, doubleQuotedFrom}
+	case v1alpha1.LanguageKotlin:
+		return []*regexp.Regexp{doubleQuotedFrom}
+	}
+	return []*regexp.Regexp{}
+}
+
+func getToRegexpsForLanguage(language v1alpha1.Language) []*regexp.Regexp {
+	switch language {
+	case v1alpha1.LanguageJavaSource:
+		return []*regexp.Regexp{doubleQuotedTo, doubleQuotedToD, doubleQuotedToF}
+	case v1alpha1.LanguageXML:
+		return []*regexp.Regexp{xmlTagTo, xmlTagToD}
+	case v1alpha1.LanguageGroovy:
+		return []*regexp.Regexp{singleQuotedTo, doubleQuotedTo, singleQuotedToD, doubleQuotedToD, singleQuotedToF, doubleQuotedToF}
+	case v1alpha1.LanguageJavaScript:
+		return []*regexp.Regexp{singleQuotedTo, doubleQuotedTo, singleQuotedToD, doubleQuotedToD, singleQuotedToF, doubleQuotedToF}
+	case v1alpha1.LanguageKotlin:
+		return []*regexp.Regexp{doubleQuotedTo, doubleQuotedToD, doubleQuotedToF}
+	}
+	return []*regexp.Regexp{}
+}
+
+func findAllDistinctStringSubmatch(data string, regexps ...*regexp.Regexp) []string {
+	candidates := make([]string, 0)
+	alreadyFound := make(map[string]bool)
+	for _, reg := range regexps {
+		hits := reg.FindAllStringSubmatch(data, -1)
+		for _, hit := range hits {
+			if hit != nil && len(hit) > 1 {
+				for _, match := range hit[1:] {
+					if _, ok := alreadyFound[match]; !ok {
+						alreadyFound[match] = true
+						candidates = append(candidates, match)
+					}
+				}
+			}
+		}
+	}
+	return candidates
+}
\ No newline at end of file
diff --git a/pkg/stub/action/integration/initialize.go b/pkg/stub/action/integration/initialize.go
index f0b5e7f..3abf51f 100644
--- a/pkg/stub/action/integration/initialize.go
+++ b/pkg/stub/action/integration/initialize.go
@@ -18,6 +18,7 @@ limitations under the License.
 package integration
 
 import (
+	"github.com/apache/camel-k/pkg/metadata"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/sirupsen/logrus"
 	"sort"
@@ -25,7 +26,6 @@ import (
 	"github.com/apache/camel-k/pkg/util"
 
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-	"github.com/apache/camel-k/pkg/discover"
 	"github.com/apache/camel-k/pkg/util/digest"
 	"github.com/operator-framework/operator-sdk/pkg/sdk"
 )
@@ -62,9 +62,11 @@ func (action *initializeAction) Handle(integration *v1alpha1.Integration) error
 		var defaultReplicas int32 = 1
 		target.Spec.Replicas = &defaultReplicas
 	}
+	// extract metadata
+	meta := metadata.Extract(target.Spec.Source)
+
 	// set the correct language
-	language := discover.Language(target.Spec.Source)
-	target.Spec.Source.Language = language
+	target.Spec.Source.Language = meta.Language
 
 	if !util.StringSliceExists(target.Spec.Dependencies, "camel:core") {
 		target.Spec.Dependencies = append(target.Spec.Dependencies, "camel:core")
@@ -76,8 +78,7 @@ func (action *initializeAction) Handle(integration *v1alpha1.Integration) error
 		target.Spec.DependenciesAutoDiscovery = &autoDiscoveryDependencies
 	}
 	if *target.Spec.DependenciesAutoDiscovery {
-		discovered := discover.Dependencies(target.Spec.Source)
-		target.Spec.Dependencies = action.mergeDependencies(target.Spec.Dependencies, discovered)
+		target.Spec.Dependencies = action.mergeDependencies(target.Spec.Dependencies, meta.Dependencies)
 	}
 	// sort the dependencies to get always the same list if they don't change
 	sort.Strings(target.Spec.Dependencies)
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index 4d94477..049b1a6 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -20,63 +20,126 @@ package trait
 import (
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/fatih/structs"
+	"reflect"
+	"strings"
 )
 
-var (
-	tBase    = newBaseTrait()
-	tService = newServiceTrait()
-	tRoute   = newRouteTrait()
-	tOwner   = newOwnerTrait()
-)
+// Catalog collects all information about traits in one place
+type Catalog struct {
+	tDeployment ITrait
+	tService    ITrait
+	tRoute      ITrait
+	tOwner      ITrait
+}
+
+// NewCatalog creates a new trait Catalog
+func NewCatalog() *Catalog {
+	return &Catalog{
+		tDeployment: newDeploymentTrait(),
+		tService:    newServiceTrait(),
+		tRoute:      newRouteTrait(),
+		tOwner:      newOwnerTrait(),
+	}
+}
+
+func (c *Catalog) allTraits() []ITrait {
+	return []ITrait{
+		c.tDeployment,
+		c.tService,
+		c.tRoute,
+		c.tOwner,
+	}
+}
 
-// customizersFor returns a Catalog for the given integration details
-func customizersFor(environment *environment) customizer {
+func (c *Catalog) traitsFor(environment *environment) []ITrait {
 	switch environment.Platform.Spec.Cluster {
 	case v1alpha1.IntegrationPlatformClusterOpenShift:
-		return compose(
-			&tBase,
-			&tService,
-			&tRoute,
-			&tOwner,
-		)
+		return []ITrait{
+			c.tDeployment,
+			c.tService,
+			c.tRoute,
+			c.tOwner,
+		}
 	case v1alpha1.IntegrationPlatformClusterKubernetes:
-		return compose(
-			&tBase,
-			&tService,
-			&tOwner,
-		)
+		return []ITrait{
+			c.tDeployment,
+			c.tService,
+			c.tOwner,
+		}
 		// case Knative: ...
 	}
 	return nil
 }
 
-func compose(traits ...customizer) customizer {
-	return &chainedCustomizer{
-		customizers: traits,
+func (c *Catalog) customize(environment *environment, resources *kubernetes.Collection) error {
+	c.configure(environment)
+	traits := c.traitsFor(environment)
+	for _, trait := range traits {
+		if trait.IsAuto() {
+			if err := trait.autoconfigure(environment, resources); err != nil {
+				return err
+			}
+		}
+		if trait.IsEnabled() {
+			if err := trait.customize(environment, resources); err != nil {
+				return err
+			}
+			environment.ExecutedTraits = append(environment.ExecutedTraits, trait.ID())
+		}
 	}
+	return nil
 }
 
-// -------------------------------------------
+// GetTrait returns the trait with the given ID
+func (c *Catalog) GetTrait(id string) ITrait {
+	for _, t := range c.allTraits() {
+		if t.ID() == ID(id) {
+			return t
+		}
+	}
+	return nil
+}
 
-type chainedCustomizer struct {
-	customizers []customizer
+func (c *Catalog) configure(env *environment) {
+	if env.Integration == nil || env.Integration.Spec.Traits == nil {
+		return
+	}
+	for id, traitSpec := range env.Integration.Spec.Traits {
+		catTrait := c.GetTrait(id)
+		if catTrait != nil {
+			traitSpec.Decode(catTrait)
+		}
+	}
 }
 
-func (c *chainedCustomizer) ID() ID {
-	return ID("")
+// ComputeTraitsProperties returns all key/value configuration properties that can be used to configure traits
+func (c *Catalog) ComputeTraitsProperties() []string {
+	results := make([]string, 0)
+	for _, trait := range c.allTraits() {
+		c.processFields(structs.Fields(trait), func(name string) {
+			results = append(results, string(trait.ID())+"."+name)
+		})
+	}
+
+	return results
 }
 
-func (c *chainedCustomizer) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
-	atLeastOne := false
-	for _, custom := range c.customizers {
-		if environment.isEnabled(custom.ID()) || environment.isAutoDetectionMode(custom.ID()) {
-			if done, err := custom.customize(environment, resources); err != nil {
-				return false, err
-			} else if done && custom.ID() != "" {
-				environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, custom.ID())
-				atLeastOne = atLeastOne || done
-			}
+func (c *Catalog) processFields(fields []*structs.Field, processor func(string)) {
+	for _, f := range fields {
+		if f.IsEmbedded() && f.IsExported() && f.Kind() == reflect.Struct {
+			c.processFields(f.Fields(), processor)
+		}
+
+		if f.IsEmbedded() {
+			continue
+		}
+
+		property := f.Tag("property")
+
+		if property != "" {
+			items := strings.Split(property, ",")
+			processor(items[0])
 		}
 	}
-	return atLeastOne, nil
 }
diff --git a/pkg/trait/base.go b/pkg/trait/deployment.go
similarity index 93%
rename from pkg/trait/base.go
rename to pkg/trait/deployment.go
index f146ce0..4225b99 100644
--- a/pkg/trait/base.go
+++ b/pkg/trait/deployment.go
@@ -28,20 +28,20 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-type baseTrait struct {
-	Trait
+type deploymentTrait struct {
+	BaseTrait `property:",squash"`
 }
 
-func newBaseTrait() baseTrait {
-	return baseTrait{
-		Trait: NewTraitWithID("base"),
+func newDeploymentTrait() *deploymentTrait {
+	return &deploymentTrait{
+		BaseTrait: newBaseTrait("deployment"),
 	}
 }
 
-func (d *baseTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
+func (d *deploymentTrait) customize(environment *environment, resources *kubernetes.Collection) error {
 	resources.Add(d.getConfigMapFor(environment))
 	resources.Add(d.getDeploymentFor(environment))
-	return true, nil
+	return nil
 }
 
 // **********************************
@@ -50,7 +50,7 @@ func (d *baseTrait) customize(environment *environment, resources *kubernetes.Co
 //
 // **********************************
 
-func (*baseTrait) getConfigMapFor(e *environment) *corev1.ConfigMap {
+func (*deploymentTrait) getConfigMapFor(e *environment) *corev1.ConfigMap {
 	// combine properties of integration with context, integration
 	// properties have the priority
 	properties := CombineConfigurationAsMap("property", e.Context, e.Integration)
@@ -86,7 +86,7 @@ func (*baseTrait) getConfigMapFor(e *environment) *corev1.ConfigMap {
 //
 // **********************************
 
-func (*baseTrait) getDeploymentFor(e *environment) *appsv1.Deployment {
+func (*deploymentTrait) getDeploymentFor(e *environment) *appsv1.Deployment {
 	sourceName := strings.TrimPrefix(e.Integration.Spec.Source.Name, "/")
 
 	// combine environment of integration with context, integration
diff --git a/pkg/trait/owner.go b/pkg/trait/owner.go
index a47f3c9..701b219 100644
--- a/pkg/trait/owner.go
+++ b/pkg/trait/owner.go
@@ -24,16 +24,16 @@ import (
 
 // ownerTrait ensures that all created resources belong to the integration being created
 type ownerTrait struct {
-	Trait
+	BaseTrait `property:",squash"`
 }
 
-func newOwnerTrait() ownerTrait {
-	return ownerTrait{
-		Trait: NewTraitWithID("owner"),
+func newOwnerTrait() *ownerTrait {
+	return &ownerTrait{
+		BaseTrait: newBaseTrait("owner"),
 	}
 }
 
-func (*ownerTrait) customize(e *environment, resources *kubernetes.Collection) (bool, error) {
+func (*ownerTrait) customize(e *environment, resources *kubernetes.Collection) error {
 	controller := true
 	blockOwnerDeletion := true
 	resources.VisitMetaObject(func(res metav1.Object) {
@@ -49,5 +49,5 @@ func (*ownerTrait) customize(e *environment, resources *kubernetes.Collection) (
 		}
 		res.SetOwnerReferences(references)
 	})
-	return true, nil
+	return nil
 }
diff --git a/pkg/trait/route.go b/pkg/trait/route.go
index 725b1a9..1553bae 100644
--- a/pkg/trait/route.go
+++ b/pkg/trait/route.go
@@ -26,31 +26,41 @@ import (
 )
 
 type routeTrait struct {
-	Trait
+	BaseTrait `property:",squash"`
 }
 
-func newRouteTrait() routeTrait {
-	return routeTrait{
-		Trait: NewTraitWithID("route"),
+func newRouteTrait() *routeTrait {
+	return &routeTrait{
+		BaseTrait: newBaseTrait("route"),
 	}
 }
 
-func (e *routeTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
-	var service *corev1.Service
+func (e *routeTrait) autoconfigure(environment *environment, resources *kubernetes.Collection) error {
+	if e.Enabled == nil {
+		hasService := e.getTargetService(environment, resources) != nil
+		e.Enabled = &hasService
+	}
+	return nil
+}
+
+func (e *routeTrait) customize(environment *environment, resources *kubernetes.Collection) error {
+	service := e.getTargetService(environment, resources)
+	if service != nil {
+		resources.Add(e.getRouteFor(environment, service))
+	}
+
+	return nil
+}
+
+func (*routeTrait) getTargetService(e *environment, resources *kubernetes.Collection) (service *corev1.Service) {
 	resources.VisitService(func(s *corev1.Service) {
 		if s.ObjectMeta.Labels != nil {
-			if intName, ok := s.ObjectMeta.Labels["camel.apache.org/integration"]; ok && intName == environment.Integration.Name {
+			if intName, ok := s.ObjectMeta.Labels["camel.apache.org/integration"]; ok && intName == e.Integration.Name {
 				service = s
 			}
 		}
 	})
-
-	if service != nil {
-		resources.Add(e.getRouteFor(environment, service))
-		return true, nil
-	}
-
-	return false, nil
+	return
 }
 
 func (*routeTrait) getRouteFor(e *environment, service *corev1.Service) *routev1.Route {
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 2afe06c..619226b 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -35,36 +35,36 @@ var webComponents = map[string]bool{
 }
 
 type serviceTrait struct {
-	Trait
+	BaseTrait `property:",squash"`
 
 	Port int `property:"port"`
 }
 
-func newServiceTrait() serviceTrait {
-	return serviceTrait{
-		Trait: NewTraitWithID("service"),
-		Port:  8080,
+func newServiceTrait() *serviceTrait {
+	return &serviceTrait{
+		BaseTrait: newBaseTrait("service"),
+		Port:      8080,
 	}
 }
 
-func (s *serviceTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
-	if environment.isAutoDetectionMode(s.ID()) && !s.requiresService(environment) {
-		return false, nil
+func (s *serviceTrait) autoconfigure(environment *environment, resources *kubernetes.Collection) error {
+	if s.Enabled == nil {
+		required := s.requiresService(environment)
+		s.Enabled = &required
 	}
-	svc, err := s.getServiceFor(environment)
-	if err != nil {
-		return false, err
+	return nil
+}
+
+func (s *serviceTrait) customize(environment *environment, resources *kubernetes.Collection) (err error) {
+	var svc *corev1.Service
+	if svc, err = s.getServiceFor(environment); err != nil {
+		return err
 	}
 	resources.Add(svc)
-	return true, nil
+	return nil
 }
 
 func (s *serviceTrait) getServiceFor(e *environment) (*corev1.Service, error) {
-	t := newServiceTrait()
-	if _, err := e.getTrait(s.ID(), &t); err != nil {
-		return nil, err
-	}
-
 	svc := corev1.Service{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Service",
@@ -83,7 +83,7 @@ func (s *serviceTrait) getServiceFor(e *environment) (*corev1.Service, error) {
 					Name:       "http",
 					Port:       80,
 					Protocol:   corev1.ProtocolTCP,
-					TargetPort: intstr.FromInt(t.Port),
+					TargetPort: intstr.FromInt(s.Port),
 				},
 			},
 			Selector: map[string]string{
diff --git a/pkg/trait/trait.go b/pkg/trait/trait.go
index 8b317ab..bdb5b0e 100644
--- a/pkg/trait/trait.go
+++ b/pkg/trait/trait.go
@@ -32,9 +32,9 @@ func ComputeDeployment(integration *v1alpha1.Integration) ([]runtime.Object, err
 		return nil, err
 	}
 	resources := kubernetes.NewCollection()
-	customizers := customizersFor(environment)
+	catalog := NewCatalog()
 	// invoke the trait framework to determine the needed resources
-	if _, err = customizers.customize(environment, resources); err != nil {
+	if err := catalog.customize(environment, resources); err != nil {
 		return nil, errors.Wrap(err, "error during trait customization")
 	}
 	return resources.Items(), nil
@@ -52,9 +52,10 @@ func newEnvironment(integration *v1alpha1.Integration) (*environment, error) {
 	}
 
 	return &environment{
-		Platform:            pl,
-		Context:             ctx,
-		Integration:         integration,
-		ExecutedCustomizers: make([]ID, 0),
+		Platform:       pl,
+		Context:        ctx,
+		Integration:    integration,
+		ExecutedTraits: make([]ID, 0),
 	}, nil
 }
+
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index f447f84..6631c8c 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -32,10 +32,11 @@ import (
 func TestOpenShiftTraits(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
-	assert.NotContains(t, env.ExecutedCustomizers, ID("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, ID("route"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
+	assert.NotEmpty(t, env.ExecutedTraits)
+	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
+	assert.NotContains(t, env.ExecutedTraits, ID("service"))
+	assert.NotContains(t, env.ExecutedTraits, ID("route"))
+	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -47,10 +48,10 @@ func TestOpenShiftTraits(t *testing.T) {
 func TestOpenShiftTraitsWithWeb(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core", "camel:undertow")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("service"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("route"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
+	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
+	assert.Contains(t, env.ExecutedTraits, ID("service"))
+	assert.Contains(t, env.ExecutedTraits, ID("route"))
+	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -74,8 +75,8 @@ func TestOpenShiftTraitsWithWebAndConfig(t *testing.T) {
 		},
 	}
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, ID("service"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("route"))
+	assert.Contains(t, env.ExecutedTraits, ID("service"))
+	assert.Contains(t, env.ExecutedTraits, ID("route"))
 	assert.NotNil(t, res.GetService(func(svc *corev1.Service) bool {
 		return svc.Name == "test" && svc.Spec.Ports[0].TargetPort.IntVal == int32(7071)
 	}))
@@ -91,8 +92,8 @@ func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) {
 		},
 	}
 	res := processTestEnv(t, env)
-	assert.NotContains(t, env.ExecutedCustomizers, ID("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, ID("route")) // No route without service
+	assert.NotContains(t, env.ExecutedTraits, ID("service"))
+	assert.NotContains(t, env.ExecutedTraits, ID("route")) // No route without service
 	assert.Nil(t, res.GetService(func(svc *corev1.Service) bool {
 		return true
 	}))
@@ -101,10 +102,10 @@ func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) {
 func TestKubernetesTraits(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "camel:core")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
-	assert.NotContains(t, env.ExecutedCustomizers, ID("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, ID("route"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
+	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
+	assert.NotContains(t, env.ExecutedTraits, ID("service"))
+	assert.NotContains(t, env.ExecutedTraits, ID("route"))
+	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -116,10 +117,10 @@ func TestKubernetesTraits(t *testing.T) {
 func TestKubernetesTraitsWithWeb(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "camel:core", "camel:servlet")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, ID("route"))
-	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
+	assert.Contains(t, env.ExecutedTraits, ID("deployment"))
+	assert.Contains(t, env.ExecutedTraits, ID("service"))
+	assert.NotContains(t, env.ExecutedTraits, ID("route"))
+	assert.Contains(t, env.ExecutedTraits, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -134,26 +135,27 @@ func TestKubernetesTraitsWithWeb(t *testing.T) {
 func TestTraitDecode(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift)
 	env.Integration.Spec.Traits = make(map[string]v1alpha1.IntegrationTraitSpec)
-	env.Integration.Spec.Traits["service"] = v1alpha1.IntegrationTraitSpec{
+	svcTrait := v1alpha1.IntegrationTraitSpec{
 		Configuration: map[string]string{
 			"enabled": "false",
 			"port":    "7071",
+			"cippa":   "lippa",
 		},
 	}
+	env.Integration.Spec.Traits["service"] = svcTrait
 
 	svc := newServiceTrait()
-	ok, err := env.getTrait(ID("service"), &svc)
+	err := svcTrait.Decode(svc)
 
 	assert.Nil(t, err)
-	assert.True(t, ok)
 	assert.Equal(t, 7071, svc.Port)
-	assert.Equal(t, true, svc.Enabled)
+	assert.Equal(t, false, svc.IsEnabled())
 }
 
 func processTestEnv(t *testing.T, env *environment) *kubernetes.Collection {
 	resources := kubernetes.NewCollection()
-	customizers := customizersFor(env)
-	_, err := customizers.customize(env, resources)
+	catalog := NewCatalog()
+	err := catalog.customize(env, resources)
 	assert.Nil(t, err)
 	return resources
 }
@@ -175,6 +177,6 @@ func createTestEnv(cluster v1alpha1.IntegrationPlatformCluster, dependencies ...
 				Cluster: cluster,
 			},
 		},
-		ExecutedCustomizers: make([]ID, 0),
+		ExecutedTraits: make([]ID, 0),
 	}
 }
diff --git a/pkg/trait/types.go b/pkg/trait/types.go
index a825c6f..23aa6a2 100644
--- a/pkg/trait/types.go
+++ b/pkg/trait/types.go
@@ -18,14 +18,8 @@ limitations under the License.
 package trait
 
 import (
-	"fmt"
-	"reflect"
-
-	"github.com/sirupsen/logrus"
-
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
-	"github.com/pkg/errors"
 )
 
 // Identifiable represent an identifiable type
@@ -36,90 +30,69 @@ type Identifiable interface {
 // ID uniquely identifies a trait
 type ID string
 
-// Trait --
-type Trait struct {
+// ITrait TODO rename
+type ITrait interface {
 	Identifiable
-
-	id      ID
-	Enabled bool `property:"enabled"`
+	// enabled tells if the trait is enabled
+	IsEnabled() bool
+	// auto determine if the trait should be configured automatically
+	IsAuto() bool
+	// autoconfigure is called before any customization to ensure the trait is fully configured
+	autoconfigure(environment *environment, resources *kubernetes.Collection) error
+	// customize executes the trait customization on the resources and return true if the resources have been changed
+	customize(environment *environment, resources *kubernetes.Collection) error
 }
 
-// ID returns the trait ID
-func (trait *Trait) ID() ID {
-	return trait.id
-}
+/* Base trait */
 
-// NewTrait creates a new trait with defaults
-func NewTrait() Trait {
-	return Trait{
-		Enabled: true,
-	}
+// BaseTrait is the root trait with noop implementations for hooks
+type BaseTrait struct {
+	id      ID
+	Enabled *bool `property:"enabled"`
+	Auto    *bool `property:"auto"`
 }
 
-// NewTraitWithID creates a new trait with defaults and given ID
-func NewTraitWithID(traitID ID) Trait {
-	return Trait{
-		id:      traitID,
-		Enabled: true,
+func newBaseTrait(id string) BaseTrait {
+	return BaseTrait{
+		id: ID(id),
 	}
 }
 
-// A Customizer performs customization of the deployed objects
-type customizer interface {
-	Identifiable
-	// Customize executes the trait customization on the resources and return true if the resources have been changed
-	customize(environment *environment, resources *kubernetes.Collection) (bool, error)
+// ID returns the identifier of the trait
+func (trait *BaseTrait) ID() ID {
+	return trait.id
 }
 
-// A environment provides the context where the trait is executed
-type environment struct {
-	Platform            *v1alpha1.IntegrationPlatform
-	Context             *v1alpha1.IntegrationContext
-	Integration         *v1alpha1.Integration
-	ExecutedCustomizers []ID
+// IsAuto determines if we should apply automatic configuration
+func (trait *BaseTrait) IsAuto() bool {
+	if trait.Auto == nil {
+		return true
+	}
+	return *trait.Auto
 }
 
-func (e *environment) getTrait(traitID ID, target interface{}) (bool, error) {
-	if spec := e.getTraitSpec(traitID); spec != nil {
-		err := spec.Decode(&target)
-		if err != nil {
-			return false, errors.Wrap(err, fmt.Sprintf("unable to convert trait %s to the target struct %s", traitID, reflect.TypeOf(target).Name()))
-		}
-
-		return true, nil
+// IsEnabled is used to determine if the trait needs to be executed
+func (trait *BaseTrait) IsEnabled() bool {
+	if trait.Enabled == nil {
+		return true
 	}
-
-	return false, nil
+	return *trait.Enabled
 }
 
-func (e *environment) getTraitSpec(traitID ID) *v1alpha1.IntegrationTraitSpec {
-	if e.Integration.Spec.Traits == nil {
-		return nil
-	}
-	if conf, ok := e.Integration.Spec.Traits[string(traitID)]; ok {
-		return &conf
-	}
+func (trait *BaseTrait) autoconfigure(environment *environment, resources *kubernetes.Collection) error {
 	return nil
 }
 
-func (e *environment) isEnabled(traitID ID) bool {
-	t := NewTrait()
-	if _, err := e.getTrait(traitID, &t); err != nil {
-		logrus.Panic(err)
-	}
-
-	return t.Enabled
+func (trait *BaseTrait) customize(environment *environment, resources *kubernetes.Collection) error {
+	return nil
 }
 
-func (e *environment) isAutoDetectionMode(traitID ID) bool {
-	spec := e.getTraitSpec(traitID)
-	if spec == nil {
-		return true
-	}
+/* Environment */
 
-	if spec.Configuration == nil {
-		return true
-	}
-
-	return spec.Configuration["enabled"] == ""
+// A environment provides the context where the trait is executed
+type environment struct {
+	Platform       *v1alpha1.IntegrationPlatform
+	Context        *v1alpha1.IntegrationContext
+	Integration    *v1alpha1.Integration
+	ExecutedTraits []ID
 }
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index 577bdb7..5b280a9 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -19,11 +19,9 @@ package trait
 
 import (
 	"fmt"
-	"reflect"
 	"strings"
 
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-	"github.com/fatih/structs"
 	"github.com/operator-framework/operator-sdk/pkg/sdk"
 	"github.com/pkg/errors"
 	"k8s.io/api/core/v1"
@@ -119,32 +117,3 @@ func CombineConfigurationAsSlice(configurationType string, context *v1alpha1.Int
 	return keys
 }
 
-// ComputeTraitsProperties --
-func ComputeTraitsProperties() []string {
-	results := make([]string, 0)
-
-	processFields(structs.Fields(tService), func(name string) {
-		results = append(results, string(tService.ID())+"."+name)
-	})
-
-	return results
-}
-
-func processFields(fields []*structs.Field, processor func(string)) {
-	for _, f := range fields {
-		if f.IsEmbedded() && f.IsExported() && f.Kind() == reflect.Struct {
-			processFields(f.Fields(), processor)
-		}
-
-		if f.IsEmbedded() {
-			continue
-		}
-
-		property := f.Tag("property")
-
-		if property != "" {
-			items := strings.Split(property, ",")
-			processor(items[0])
-		}
-	}
-}


Mime
View raw message