ISIS-1223 Upgrade Wicket to 7.x Copy temporarily Wicket's Guice classes to workaround issue https://issues.apache.org/jira/browse/WICKET-6020. Once Wicket 7.2.0 is released these classes should be removed!
Project: http://git-wip-us.apache.org/repos/asf/isis/repo Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/5625c6c4 Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/5625c6c4 Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/5625c6c4 Branch: refs/heads/master Commit: 5625c6c4cec59c57e00cffa0159a89795b46addc Parents: 8eab457 Author: Martin Tzvetanov Grigorov <[email protected]> Authored: Mon Nov 2 21:35:42 2015 +0100 Committer: Martin Tzvetanov Grigorov <[email protected]> Committed: Mon Nov 2 21:35:42 2015 +0100 ---------------------------------------------------------------------- .../wicket/viewer/IsisWicketApplication.java | 4 +- .../viewer/guice/GuiceComponentInjector.java | 137 ++++++++++++++++ .../viewer/guice/GuiceFieldValueFactory.java | 161 ++++++++++++++++++ .../viewer/guice/GuiceProxyTargetLocator.java | 162 +++++++++++++++++++ 4 files changed, 461 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/isis/blob/5625c6c4/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java ---------------------------------------------------------------------- diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java index 401a4b5..a04290e 100644 --- a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java +++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/IsisWicketApplication.java @@ -46,7 +46,6 @@ import org.apache.wicket.SharedResources; import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication; import org.apache.wicket.authroles.authentication.AuthenticatedWebSession; import org.apache.wicket.core.request.mapper.MountedMapper; -import org.apache.wicket.guice.GuiceComponentInjector; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.filter.JavaScriptFilteredIntoFooterHeaderResponse; import org.apache.wicket.markup.html.IHeaderContributor; @@ -100,6 +99,7 @@ import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistry; import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistryAccessor; import org.apache.isis.viewer.wicket.ui.pages.accmngt.AccountConfirmationMap; import org.apache.isis.viewer.wicket.ui.panels.PanelUtil; +import org.apache.isis.viewer.wicket.viewer.guice.GuiceComponentInjector; import org.apache.isis.viewer.wicket.viewer.integration.isis.DeploymentTypeWicketAbstract; import org.apache.isis.viewer.wicket.viewer.integration.isis.WicketServer; import org.apache.isis.viewer.wicket.viewer.integration.isis.WicketServerPrototype; @@ -689,8 +689,6 @@ public class IsisWicketApplication protected void initWicketComponentInjection(final Injector injector) { - // if serializable, then brings in dependency on cglib, and in turn asm. - // This would block us from migrating to DN 4.0.x getComponentInstantiationListeners().add(new GuiceComponentInjector(this, injector, false)); } http://git-wip-us.apache.org/repos/asf/isis/blob/5625c6c4/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceComponentInjector.java ---------------------------------------------------------------------- diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceComponentInjector.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceComponentInjector.java new file mode 100644 index 0000000..b80ba1d --- /dev/null +++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceComponentInjector.java @@ -0,0 +1,137 @@ +/* + * 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 org.apache.isis.viewer.wicket.viewer.guice; + +import com.google.inject.Guice; +import com.google.inject.ImplementedBy; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Stage; +import org.apache.wicket.Application; +import org.apache.wicket.Component; +import org.apache.wicket.IBehaviorInstantiationListener; +import org.apache.wicket.Session; +import org.apache.wicket.application.IComponentInstantiationListener; +import org.apache.wicket.behavior.Behavior; +import org.apache.wicket.guice.GuiceInjectorHolder; +import org.apache.wicket.injection.IFieldValueFactory; +import org.apache.wicket.model.Model; + +/** + * Injects field members of components and behaviors using Guice. + * <p> + * Add this to your application in its {@link Application#init()} method like so: + * + * <pre> + * getComponentInstantiationListeners().add(new GuiceComponentInjector(this)); + * </pre> + * + * <p> + * There are different constructors for this object depending on how you want to wire things. See + * the javadoc for the constructors for more information. + * </p> + * <p> + * Only Wicket {@link Component}s and {@link Behavior}s are automatically injected, other classes + * such as {@link Session}, {@link Model}, and any other POJO can be injected by calling + * <code>Injector.get().inject(this)</code> in their constructor. + * </p> + * + * @author Alastair Maw + */ +public class GuiceComponentInjector extends org.apache.wicket.injection.Injector + implements + IComponentInstantiationListener, + IBehaviorInstantiationListener +{ + private final IFieldValueFactory fieldValueFactory; + + /** + * Creates a new Wicket GuiceComponentInjector instance. + * <p> + * Internally this will create a new Guice {@link Injector} instance, with no {@link Module} + * instances. This is only useful if your beans have appropriate {@link ImplementedBy} + * annotations on them so that they can be automatically picked up with no extra configuration + * code. + * + * @param app + */ + public GuiceComponentInjector(final Application app) + { + this(app, new Module[0]); + } + + /** + * Creates a new Wicket GuiceComponentInjector instance, using the supplied Guice {@link Module} + * instances to create a new Guice {@link Injector} instance internally. + * + * @param app + * @param modules + */ + public GuiceComponentInjector(final Application app, final Module... modules) + { + this(app, Guice.createInjector(app.usesDeploymentConfig() ? Stage.PRODUCTION + : Stage.DEVELOPMENT, modules), true); + } + + /** + * Constructor + * + * @param app + * @param injector + */ + public GuiceComponentInjector(final Application app, final Injector injector) + { + this(app, injector, true); + } + + /** + * Creates a new Wicket GuiceComponentInjector instance, using the provided Guice + * {@link Injector} instance. + * + * @param app + * @param injector + * @param wrapInProxies + * whether or not wicket should wrap dependencies with specialized proxies that can + * be safely serialized. in most cases this should be set to true. + */ + public GuiceComponentInjector(final Application app, final Injector injector, + final boolean wrapInProxies) + { + app.setMetaData(GuiceInjectorHolder.INJECTOR_KEY, new GuiceInjectorHolder(injector)); + fieldValueFactory = new GuiceFieldValueFactory(wrapInProxies); + app.getBehaviorInstantiationListeners().add(this); + bind(app); + } + + @Override + public void inject(final Object object) + { + inject(object, fieldValueFactory); + } + + @Override + public void onInstantiation(final Component component) + { + inject(component); + } + + @Override + public void onInstantiation(Behavior behavior) + { + inject(behavior); + } +} http://git-wip-us.apache.org/repos/asf/isis/blob/5625c6c4/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceFieldValueFactory.java ---------------------------------------------------------------------- diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceFieldValueFactory.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceFieldValueFactory.java new file mode 100644 index 0000000..70d3568 --- /dev/null +++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceFieldValueFactory.java @@ -0,0 +1,161 @@ +/* + * 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 org.apache.isis.viewer.wicket.viewer.guice; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.concurrent.ConcurrentMap; + +import javax.inject.Qualifier; + +import org.apache.wicket.injection.IFieldValueFactory; +import org.apache.wicket.proxy.LazyInitProxyFactory; + +import com.google.inject.BindingAnnotation; +import com.google.inject.Inject; +import org.apache.wicket.util.lang.Generics; + +/** + * + */ +public class GuiceFieldValueFactory implements IFieldValueFactory +{ + private final ConcurrentMap<GuiceProxyTargetLocator, Object> cache = Generics.newConcurrentHashMap(); + private static final Object NULL_SENTINEL = new Object(); + + private final boolean wrapInProxies; + + /** + * Construct. + * + * @param wrapInProxies + */ + GuiceFieldValueFactory(final boolean wrapInProxies) + { + this.wrapInProxies = wrapInProxies; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getFieldValue(final Field field, final Object fieldOwner) + { + Object target = null; + + if (supportsField(field)) + { + Inject injectAnnotation = field.getAnnotation(Inject.class); + javax.inject.Inject javaxInjectAnnotation = field.getAnnotation(javax.inject.Inject.class); + if (!Modifier.isStatic(field.getModifiers()) && (injectAnnotation != null || javaxInjectAnnotation != null)) + { + try + { + boolean optional = injectAnnotation != null && injectAnnotation.optional(); + Annotation bindingAnnotation = findBindingAnnotation(field.getAnnotations()); + final GuiceProxyTargetLocator locator = new GuiceProxyTargetLocator(field, bindingAnnotation, optional); + + Object cachedValue = cache.get(locator); + if (cachedValue != null) + { + return cachedValue == NULL_SENTINEL ? null : cachedValue; + } + + target = locator.locateProxyTarget(); + if (target == null) + { + // Optional without a binding, return null + } + else + { + if (wrapInProxies) + { + target = LazyInitProxyFactory.createProxy(field.getType(), locator); + } + } + + if (locator.isSingletonScope()) + { + Object tmpTarget = cache.putIfAbsent(locator, target == null ? NULL_SENTINEL : target); + if (tmpTarget != null) + { + target = tmpTarget; + } + } + + if (!field.isAccessible()) + { + field.setAccessible(true); + } + } + catch (MoreThanOneBindingException e) + { + throw new RuntimeException( + "Can't have more than one BindingAnnotation on field " + field.getName() + + " of class " + fieldOwner.getClass().getName()); + } + } + } + + return target == NULL_SENTINEL ? null : target; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean supportsField(final Field field) + { + return field.isAnnotationPresent(Inject.class) || field.isAnnotationPresent(javax.inject.Inject.class); + } + + /** + * + * @param annotations + * @return Annotation + * @throws MoreThanOneBindingException + */ + private Annotation findBindingAnnotation(final Annotation[] annotations) + throws MoreThanOneBindingException + { + Annotation bindingAnnotation = null; + + // Work out if we have a BindingAnnotation on this parameter. + for (Annotation annotation : annotations) + { + if (annotation.annotationType().getAnnotation(BindingAnnotation.class) != null || + annotation.annotationType().getAnnotation(Qualifier.class) != null) + { + if (bindingAnnotation != null) + { + throw new MoreThanOneBindingException(); + } + bindingAnnotation = annotation; + } + } + return bindingAnnotation; + } + + /** + * + */ + public static class MoreThanOneBindingException extends Exception + { + private static final long serialVersionUID = 1L; + } +} http://git-wip-us.apache.org/repos/asf/isis/blob/5625c6c4/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceProxyTargetLocator.java ---------------------------------------------------------------------- diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceProxyTargetLocator.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceProxyTargetLocator.java new file mode 100644 index 0000000..dcf11e7 --- /dev/null +++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/guice/GuiceProxyTargetLocator.java @@ -0,0 +1,162 @@ +/* + * 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 org.apache.isis.viewer.wicket.viewer.guice; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Type; + +import com.google.inject.ConfigurationException; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Scopes; +import com.google.inject.TypeLiteral; +import org.apache.wicket.Application; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.guice.GuiceInjectorHolder; +import org.apache.wicket.proxy.IProxyTargetLocator; +import org.apache.wicket.core.util.lang.WicketObjects; +import org.apache.wicket.util.lang.Objects; + +class GuiceProxyTargetLocator implements IProxyTargetLocator +{ + private static final long serialVersionUID = 1L; + + private final Annotation bindingAnnotation; + + private final boolean optional; + + private final String className; + + private final String fieldName; + + private Boolean isSingletonCache = null; + + public GuiceProxyTargetLocator(final Field field, final Annotation bindingAnnotation, + final boolean optional) + { + this.bindingAnnotation = bindingAnnotation; + this.optional = optional; + className = field.getDeclaringClass().getName(); + fieldName = field.getName(); + } + + @Override + public Object locateProxyTarget() + { + Injector injector = getInjector(); + + final Key<?> key = newGuiceKey(); + + // if the Inject annotation is marked optional and no binding is found + // then skip this injection (WICKET-2241) + if (optional) + { + // Guice 2.0 throws a ConfigurationException if no binding is find while 1.0 simply + // returns null. + try + { + if (injector.getBinding(key) == null) + { + return null; + } + } + catch (RuntimeException e) + { + return null; + } + } + + return injector.getInstance(key); + } + + private Key<?> newGuiceKey() + { + final Type type; + try + { + Class<?> clazz = WicketObjects.resolveClass(className); + final Field field = clazz.getDeclaredField(fieldName); + type = field.getGenericType(); + } + catch (Exception e) + { + throw new WicketRuntimeException("Error accessing member: " + fieldName + + " of class: " + className, e); + } + + // using TypeLiteral to retrieve the key gives us automatic support for + // Providers and other injectable TypeLiterals + if (bindingAnnotation == null) + { + return Key.get(TypeLiteral.get(type)); + } + else + { + return Key.get(TypeLiteral.get(type), bindingAnnotation); + } + } + + public boolean isSingletonScope() + { + if (isSingletonCache == null) + { + try + { + isSingletonCache = Scopes.isSingleton(getInjector().getBinding(newGuiceKey())); + } + catch (ConfigurationException ex) + { + // No binding, if optional can pretend this is null singleton + if (optional) + isSingletonCache = true; + else + throw ex; + } + } + return isSingletonCache; + } + + private Injector getInjector() + { + final GuiceInjectorHolder holder = Application.get().getMetaData( + GuiceInjectorHolder.INJECTOR_KEY); + + return holder.getInjector(); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (!(o instanceof GuiceProxyTargetLocator)) + return false; + GuiceProxyTargetLocator that = (GuiceProxyTargetLocator) o; + return Objects.equal(optional, that.optional) && + Objects.equal(bindingAnnotation, that.bindingAnnotation) && + Objects.equal(className, that.className) && + Objects.equal(fieldName, that.fieldName); + } + + @Override + public int hashCode() + { + return Objects.hashCode(bindingAnnotation, optional, className, fieldName); + } + +}
