-
Notifications
You must be signed in to change notification settings - Fork 130
Plugin Framework #1435
Plugin Framework #1435
Changes from 1 commit
49ebfc3
0dead54
fb486a2
4f0b4ef
6dfb20a
613705b
ce7928b
042150d
3ae2fce
b9b5e46
3afabf6
efeabf7
4640c8c
94f0ba8
92b383b
581c842
e5a4583
ebd4246
a79fb04
9c96867
2c81b2b
b0d0774
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* Copyright 2018 ConsenSys AG. | ||
* | ||
* Licensed 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. | ||
*/ | ||
|
||
apply plugin: 'java-library' | ||
|
||
jar { | ||
baseName 'pantheon-plugins' | ||
manifest { | ||
attributes( | ||
'Specification-Title': baseName, | ||
'Specification-Version': project.version, | ||
'Implementation-Title': baseName, | ||
'Implementation-Version': calculateVersion() | ||
) | ||
} | ||
} | ||
|
||
dependencies { implementation('com.google.guava:guava') } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* Copyright 2019 ConsenSys AG. | ||
* | ||
* Licensed 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 tech.pegasys.pantheon.plugins; | ||
|
||
import java.util.Optional; | ||
|
||
public interface PantheonContext { | ||
|
||
<T> Optional<T> getService(Class<T> serviceType); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* Copyright 2019 ConsenSys AG. | ||
* | ||
* Licensed 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 tech.pegasys.pantheon.plugins; | ||
|
||
public interface PantheonPlugin { | ||
|
||
void register(PantheonContext context); | ||
|
||
void start(); | ||
|
||
void stop(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* Copyright 2019 ConsenSys AG. | ||
* | ||
* Licensed 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 tech.pegasys.pantheon.plugins.internal; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
import static com.google.common.base.Preconditions.checkState; | ||
|
||
import tech.pegasys.pantheon.plugins.PantheonContext; | ||
import tech.pegasys.pantheon.plugins.PantheonPlugin; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.ServiceLoader; | ||
|
||
public class PantheonPluginContextImpl implements PantheonContext { | ||
|
||
private enum Lifecycle { | ||
UNINITIALZED, | ||
REGISTERING, | ||
REGISTERED, | ||
STARTING, | ||
STARTED, | ||
STOPPING, | ||
STOPPED | ||
} | ||
|
||
private Lifecycle state = Lifecycle.UNINITIALZED; | ||
private final Map<Class<?>, ? super Object> serviceRegistry = new HashMap<>(); | ||
private ServiceLoader<PantheonPlugin> plugins; | ||
|
||
public void addService(final Class<?> serviceType, final Object service) { | ||
checkArgument(serviceType.isInterface(), "Services must be Java interfaces."); | ||
checkArgument( | ||
serviceType.isInstance(service), | ||
"The service registered with a type must implement that type"); | ||
serviceRegistry.put(serviceType, service); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public <T> Optional<T> getService(final Class<T> serviceType) { | ||
return (Optional<T>) Optional.ofNullable((Map<Class<T>, T>) serviceRegistry.get(serviceType)); | ||
} | ||
|
||
public void registerPlugins() { | ||
checkState(state == Lifecycle.UNINITIALZED, "PantheonContext has already beein registered."); | ||
state = Lifecycle.REGISTERING; | ||
plugins = ServiceLoader.load(PantheonPlugin.class); | ||
for (final PantheonPlugin plugin : plugins) { | ||
plugin.register(this); | ||
} | ||
state = Lifecycle.REGISTERED; | ||
} | ||
|
||
public void startPlugins() { | ||
checkState( | ||
state == Lifecycle.REGISTERED, | ||
"PantheonContext should be in state %s but it was in %s", | ||
Lifecycle.REGISTERED, | ||
state); | ||
state = Lifecycle.STARTING; | ||
plugins = ServiceLoader.load(PantheonPlugin.class); | ||
for (final PantheonPlugin plugin : plugins) { | ||
plugin.start(); | ||
} | ||
state = Lifecycle.STARTED; | ||
} | ||
|
||
public void stopPlugins() { | ||
checkState( | ||
state == Lifecycle.STARTED, | ||
"PantheonContext should be in state %s but it was in %s", | ||
Lifecycle.STARTED, | ||
state); | ||
state = Lifecycle.STARTING; | ||
plugins = ServiceLoader.load(PantheonPlugin.class); | ||
for (final PantheonPlugin plugin : plugins) { | ||
plugin.stop(); | ||
} | ||
state = Lifecycle.STARTED; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/** This package will be hidden from external users once Pantheon migrates to Java 11. */ | ||
package tech.pegasys.pantheon.plugins.internal; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright 2019 ConsenSys AG. | ||
* | ||
* Licensed 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 tech.pegasys.pantheon.plugins.services; | ||
|
||
import java.util.function.Consumer; | ||
|
||
public interface PantheonEvents { | ||
|
||
/** | ||
* Returns the raw RLP of a block that Pantheon has receieved and that has passed basic validation | ||
* checks. | ||
* | ||
* @param blockJSONListener The listener that will accept a JSON string as the event. | ||
* @return an object to be used as an identifier when de-registering the event. | ||
*/ | ||
Object addBlockAddedListener(Consumer<String> blockJSONListener); | ||
|
||
/** | ||
* Remove the blockAdded listener from pantheon notifications. | ||
* | ||
* @param listenerIdentifier The instance that was returned from addBlockAddedListener; | ||
*/ | ||
void removeBlockAddedObserver(Object listenerIdentifier); | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,27 @@ | ||||||
/* | ||||||
* Copyright 2019 ConsenSys AG. | ||||||
* | ||||||
* Licensed 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 tech.pegasys.pantheon.plugins.services; | ||||||
|
||||||
/** This service will be available during the registration callbacks. */ | ||||||
public interface PicoCLIOptions { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just checking my understanding here. Seems like the idea in tying this class to PicoCLI rather than making it something more generic like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1, strongly coupled to Pico cli library. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, graceful migration. |
||||||
|
||||||
/** | ||||||
* During the registration callback plugins can register CLI options that should be added to | ||||||
* Pantheon's CLI startup. | ||||||
* | ||||||
* @param namespace A namespace prefix. All registered options must start with this prefix | ||||||
* @param optionObject The instance of the object to be inspected. PicoCLI will reflect the fields | ||||||
* of this object to extract the CLI options. | ||||||
*/ | ||||||
void addPicoCLIOptions(String namespace, Object optionObject); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(optional) We could still probably make the method name more generic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it is very tied to PicoCLI's style I feel we should communicate that wherever possible. |
||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious - why go with this generic pattern rather than just explicitly exposing some services like
registerCLIOptions
,getPantheonEvents
, etc? Is the idea that this makes it easier for us to remove services in the future?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also wondering if we shouldn't just return the service class directly rather than wrapping in it in an
Optional
? And in the case where a plugin asks for a service that isn't registered, we could just throw an exception and disable that plugin. Seems safer to disable plugins that might be missing required services.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is so we can expose more services later and not have to mutate the API. In a future revision the plugins themselves could expose services.
The Optional is there in case plugins want to do graceful degraded service. A plugin may want a service but not require it. For example a plugin that interacts with consensus engines would only expect one to be operating but would go down the list of possible engines that expose services to the plugin architecture.