Skip to content
This repository has been archived by the owner on Feb 9, 2021. It is now read-only.

Commit

Permalink
UF-317: Dynamic GWT plugin support (details below) (#464)
Browse files Browse the repository at this point in the history
Enhance run-time plugin mechanism to support client-side
plugin development using the GWT/Errai/UberFire
programming model.

This has been a long time coming with the lion's share
of the work in Errai (IOC/CDI support for JS types) and
GWT (JsInterop) respectively.

UberFire enhancements:

- Add minimal new API surface to mark perspectives,
screens, and editors as dynamic

- Refactor Activity class hierarchy and related types
to allow sharing of these types across different
scripts using JsInterop

- Mark types as @jstypes were possible to export
method names (need stable method names across different
compilation units)

- Add @JSignore to methods that can't be exported because
they use parameter or return types that can't be used
from JavaScript e.g. widgets

- Provide and use alternative default methods usable from
JS where possible

- Disable managed beans on classpath that are not needed
in plugins based on system property

- Monitor the /plugins dir for .jar files, extract them
and publish the contained JS files

- Load plugins using Errai's ScriptRegistry and
ScriptInjectionFilter to ensure all plugin scripts are
executed before the actual app bootstraps (required to
make sure all plugins and its managed beans can be
discovered by the main application)

- Enhance uberfire-showcase to demonstrate this new
plugin functionality (see README.md in uberfire-showcase/
uberfire-dynamic-plugin for details)

- Add test coverage for client-side dynamic activity
generation, server-side plugin monitoring and
processing
  • Loading branch information
csadilek authored and ederign committed Aug 11, 2016
1 parent 728a955 commit b3cc581
Show file tree
Hide file tree
Showing 197 changed files with 4,049 additions and 598 deletions.
Expand Up @@ -41,6 +41,8 @@ public interface ObservablePath extends Path,
void onConcurrentCopy( final ParameterizedCommand<OnConcurrentCopyEvent> command );

ObservablePath wrap( final Path path );

Path getOriginal();

public interface OnConcurrentUpdateEvent extends SessionInfo {

Expand Down
3 changes: 3 additions & 0 deletions uberfire-api/src/main/java/org/uberfire/backend/vfs/Path.java
Expand Up @@ -16,6 +16,9 @@

package org.uberfire.backend.vfs;

import jsinterop.annotations.JsType;

@JsType
public interface Path extends Comparable<Path> {

String getFileName();
Expand Down
Expand Up @@ -80,7 +80,7 @@ public ObservablePath wrap( final Path path ) {
// Key for Activity and Place Management). However re-hydration stores the PartDefinition in a HashSet using the incorrect hashCode. By not
// storing the "original" in the serialized form we can guarantee hashCodes in de-serialized PerspectiveDefinitions remain immutable.
// See https://bugzilla.redhat.com/show_bug.cgi?id=1200472 for the re-producer.
private Path getOriginal() {
public Path getOriginal() {
if ( this.original == null ) {
wrap( this.path );
}
Expand Down
3 changes: 3 additions & 0 deletions uberfire-api/src/main/java/org/uberfire/mvp/Command.java
Expand Up @@ -15,11 +15,14 @@
*/
package org.uberfire.mvp;

import jsinterop.annotations.JsType;

/**
* A command representing a future activity. This was deliberately created in
* addition to the existing GWT Command to allow better re-use of menu
* structures when a WorkbenchPart is embedded within Eclipse.
*/
@JsType
public interface Command {

public void execute();
Expand Down
21 changes: 20 additions & 1 deletion uberfire-api/src/main/java/org/uberfire/mvp/PlaceRequest.java
Expand Up @@ -19,15 +19,19 @@
import java.util.Map;
import java.util.Set;

import org.uberfire.backend.vfs.Path;
import org.uberfire.mvp.impl.DefaultPlaceRequest;

import jsinterop.annotations.JsType;

/**
* A request to navigate to a particular UberFire Workbench Place (a WorkbenchPerspective, a WorkbenchScreen, or a
* WorkbenchEditor). Can include optional state parameters that are made available to the requested place.
* <p>
* Place requests can be serialized to and created from a valid URL fragment identifier (the string that goes after the
* {@code #} in the browser's location bar).
*/
@JsType
public interface PlaceRequest {

public static final PlaceRequest NOWHERE = new DefaultPlaceRequest( "NOWHERE" );
Expand Down Expand Up @@ -55,5 +59,20 @@ PlaceRequest addParameter( final String name,
* Indicates whether or not the Workbench framework should add a browser history item when navigating to this place.
*/
boolean isUpdateLocationBarAllowed();


/**
* Returns the path associated with this {@link PlaceRequest}.
*/
default Path getPath() {
// TODO go over all UF public API and start using Optional
return null;
}

/**
* Invokes {@link #toString()} but exported to JavaScript so it can be invoked from different scripts.
*/
default String asString() {
return this.toString();
}

}
Expand Up @@ -155,7 +155,7 @@ public String getFullIdentifier() {
StringBuilder fullIdentifier = new StringBuilder();
fullIdentifier.append( this.getIdentifier() );

if ( this.getParameterNames().size() > 0 ) {
if ( !this.getParameterNames().isEmpty() ) {
fullIdentifier.append( "?" );
}
for ( String name : this.getParameterNames() ) {
Expand Down
@@ -0,0 +1,45 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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 org.uberfire.mvp.impl;

import org.uberfire.backend.vfs.Path;

import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsType;

/**
* A {@link PathPlaceRequest} originating from an external script.
*/
@JsType
public class ExternalPathPlaceRequest extends DefaultPlaceRequest {

private Path path;

public ExternalPathPlaceRequest(Path path) {
this.path = path;
}

@Override
public Path getPath() {
return path;
}

@JsIgnore
public static ExternalPathPlaceRequest create(PathPlaceRequest request) {
return new ExternalPathPlaceRequest(request.getPath().getOriginal());
}

}
Expand Up @@ -65,6 +65,7 @@ public PathPlaceRequest( final Path path,
this.parameters.putAll( parameters );
}

@Override
public ObservablePath getPath() {
return path;
}
Expand Down
97 changes: 97 additions & 0 deletions uberfire-api/src/main/java/org/uberfire/plugin/PluginUtil.java
@@ -0,0 +1,97 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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 org.uberfire.plugin;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsType;

import static org.uberfire.commons.validation.PortablePreconditions.checkNotNull;

/**
* Utilities for working with external (GWT-compiled) plugins.
*/
public class PluginUtil {

private PluginUtil() {
}

/**
* {@link List} is a {@link JsType} but {@link Collection#iterator()} is
* {@link JsIgnore}d and therefore not exported to JavaScript.
*
* This method takes a list and converts it to a new list so it can be
* iterated over in the current script (e.g. using enhanced for loops), even
* if the instance was provided by an external (GWT-compiled) script.
*
* @param externalList
* A list, possibly provided by an external script. Must not be null.
* @return an immutable list containing the original elements of the
* provided list
*/
public static <T> List<T> ensureIterable( List<T> externalList ) {
checkNotNull( "externalList",
externalList );

// toArray(T[]) is @JsIgnored
@SuppressWarnings("unchecked")
final List<T> tmp = (List<T>) Arrays.asList( externalList.toArray() );
return Collections.unmodifiableList( tmp );
}

/**
* {@link Set} is a {@link JsType} but {@link Collection#iterator()} is
* {@link JsIgnore}d and therefore not exported to JavaScript.
*
* This method takes a set and converts it to a new set so it can be
* iterated over in the current script (e.g. using enhanced for loops), even
* if the instance was provided by an external (GWT-compiled) script.
*
* @param externalSet
* A set, possibly provided by an external script. Must not be null.
* @return an immutable set containing the original elements of the provided
* set
*/
public static <T> Set<T> ensureIterable( Set<T> externalSet ) {
checkNotNull( "externalSet",
externalSet );

// toArray(T[]) is @JsIgnored
@SuppressWarnings("unchecked")
final List<T> tmp = (List<T>) Arrays.asList( externalSet.toArray() );
return Collections.unmodifiableSet( new HashSet<T>( tmp ) );
}

/**
* {@link Integer} is not a {@link JsType} and can't be shared across
* scripts.
*
* This method converts a regular int to an {@link Integer} using -1 as a
* placeholder for null.
*
* @param value
* @return boxed {@link Integer}, null if provided value is -1.
*/
public static Integer toInteger(int value) {
return (value != -1) ? value : null;
}
}
28 changes: 24 additions & 4 deletions uberfire-api/src/main/java/org/uberfire/security/Resource.java
Expand Up @@ -19,14 +19,18 @@
import java.util.Collections;
import java.util.List;

import jsinterop.annotations.JsType;

/**
* A generic interface for modelling resources, like UI assets: perspectives, screens or
* editors or even backend resources like repositories, projects, data objects, etc...
*/
@JsType
public interface Resource {

/**
* An identifier that is unique among all the resources of the same type (see {@link Resource#getResourceType()}).
* An identifier that is unique among all the resources of the same type
* (see {@link Resource#getResourceType()}).
*/
String getIdentifier();

Expand All @@ -40,12 +44,28 @@ default ResourceType getResourceType() {
/**
* A list of dependent resources.
*
* <p>The dependency list is used for instance to determine if a user can access a given resource. Should
* the access to all its dependencies is denied, it is denied for this instance as well.</p>
* <p>
* The dependency list is used for instance to determine if a user can
* access a given resource. Should the access to all its dependencies is
* denied, it is denied for this instance as well.
* </p>
*
* @return A list of resources or null if this resource has no dependencies.
* @return A list of resources, never null.
*/
default List<Resource> getDependencies() {
return Collections.emptyList();
}

/**
* Check if this resource is of the provided type. The type name is used
* here so this method can be used on instances from external (GWT-compiled)
* scripts (enum equals and instanceof doesn't work across script boundaries).
*
* @param typeName
* the resource type's name
* @return true if the resource has the provided type, otherwise false.
*/
default boolean isType( String typeName ) {
return getResourceType().getName().equalsIgnoreCase( typeName );
}
}
Expand Up @@ -16,13 +16,16 @@

package org.uberfire.security;

import jsinterop.annotations.JsType;

/**
* An action represents something that someone can do over a resource.
* Can vary from a complex UI feature to a low-level action.
*
* <p>This interface is intended to be extended by the different {@link Resource} types. It is up to every
* resource type implementation to define the list of available actions.</p>
*/
@JsType
public interface ResourceAction {

/**
Expand Down
Expand Up @@ -16,9 +16,12 @@

package org.uberfire.security;

import jsinterop.annotations.JsType;

/**
* Type interface for {@link Resource} instances
*/
@JsType
public interface ResourceType {

/**
Expand Down
Expand Up @@ -21,28 +21,36 @@
import org.uberfire.security.ResourceRef;
import org.uberfire.security.ResourceType;

import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsType;

/**
* A instance holding a resource reference plus an action
*/
@JsType
public class ResourceActionRef {

private Resource resource = null;
private ResourceAction action = null;

@JsIgnore
public ResourceActionRef(Resource resource) {
this(resource,ResourceAction.READ);
}

@JsIgnore
public ResourceActionRef(Resource resource, ResourceAction action) {
this.resource = resource;
this.action = action;
}

@JsIgnore
public ResourceActionRef(ResourceType type, ResourceAction action) {
this.resource = new ResourceRef(null, type);
this.action = action;
}

@JsIgnore
public ResourceActionRef(ResourceType type, Resource resource, ResourceAction action) {
this.resource = resource != null ? resource : new ResourceRef(null, type);
this.action = action;
Expand Down
Expand Up @@ -18,6 +18,9 @@

import org.uberfire.security.Resource;

import jsinterop.annotations.JsType;

@JsType
public interface RuntimeResource extends Resource {

}

0 comments on commit b3cc581

Please sign in to comment.