Index: src/main/java/com/sun/enterprise/config/serverbeans/ConnectorResource.java =================================================================== --- src/main/java/com/sun/enterprise/config/serverbeans/ConnectorResource.java (revision 51804) +++ src/main/java/com/sun/enterprise/config/serverbeans/ConnectorResource.java (working copy) @@ -40,6 +40,8 @@ package com.sun.enterprise.config.serverbeans; +import com.sun.enterprise.config.serverbeans.ConnectorConnectionPool; +import com.sun.enterprise.config.serverbeans.customvalidators.ReferenceConstraint; import com.sun.enterprise.config.serverbeans.customvalidators.ResourcePoolReferenceConstraint; import org.jvnet.hk2.config.Attribute; import org.jvnet.hk2.config.Element; @@ -74,7 +76,8 @@ @RestRedirect(opType = RestRedirect.OpType.POST, commandName = "create-connector-resource"), @RestRedirect(opType = RestRedirect.OpType.DELETE, commandName = "delete-connector-resource") }) -@ResourcePoolReferenceConstraint(message="{resource.invalid}", payload=ConnectorResource.class) +//@ResourcePoolReferenceConstraint(message="{resource.invalid}", payload=ConnectorResource.class) +@ReferenceConstraint(skipDuringCreation=true, payload=ConnectorResource.class) public interface ConnectorResource extends ConfigBeanProxy, Injectable, Resource, PropertyBag, BindableResource, Payload { @@ -87,6 +90,7 @@ */ @Attribute @NotNull + @ReferenceConstraint.RemoteKey(message="{resourceref.invalid.poolname}", type=ConnectorConnectionPool.class) String getPoolName(); /** Index: src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/LocalStrings.properties =================================================================== --- src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/LocalStrings.properties (revision 51804) +++ src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/LocalStrings.properties (working copy) @@ -52,3 +52,6 @@ connpool.custom.validation.classname.mandatory=Must specify 'validation-class-name' when 'connection-validation' is enabled and 'validation-method' is 'custom-validation'. Please specify 'validation-class-name' before enabling 'custom-validation' based validation. ref.invalid=lb-config can contain references to either server-ref or cluster-ref but not both. + +referenceValidator.not.getter=RemoteKey annotation must be on getter method only. +referenceValidator.not.string=Remote key must be String. Index: src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/ReferenceConstraint.java =================================================================== --- src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/ReferenceConstraint.java (revision 0) +++ src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/ReferenceConstraint.java (working copy) @@ -0,0 +1,88 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package com.sun.enterprise.config.serverbeans.customvalidators; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import java.lang.annotation.Target; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.ElementType; +import javax.validation.Constraint; +import javax.validation.Payload; +import org.jvnet.hk2.config.ConfigBeanProxy; + +/** Annotated {@code ConfigBeanProxy} class contains at least one {@code String} + * field, which value must point to key attribute of some other existing + * {@code ConfigBeanProxy} instance.
+ * Use {@link ReferenceConstraint.RemoteKey} annotation on appropriate getters + * to define such fields.
+ * This constrain is supported for {@code ConfigBeanProxy} only. + * + * @author Martin Mares + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Constraint(validatedBy = ReferenceValidator.class) +public @interface ReferenceConstraint { + String message() default "Invalid reference in provided configuration."; + Class[] groups() default {}; + Class[] payload() default {}; + + /** In GlassFish a lot of configurations are made in batch and its references + * could not be fulfilled during creation process. + */ + boolean skipDuringCreation(); + + /** This annotation get sense only on getter method and in combination with + * {@link ReferenceConstraint} annotation on the class. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @Documented + public @interface RemoteKey { + String message() default ""; + /** Type of {@code ConfigBeanProxy} where this remote key points to. + */ + Class type(); + } +} Index: src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/ReferenceValidator.java =================================================================== --- src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/ReferenceValidator.java (revision 0) +++ src/main/java/com/sun/enterprise/config/serverbeans/customvalidators/ReferenceValidator.java (working copy) @@ -0,0 +1,159 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package com.sun.enterprise.config.serverbeans.customvalidators; + +import com.sun.enterprise.util.LocalStringManagerImpl; +import com.sun.logging.LogDomains; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.logging.Logger; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.UnexpectedTypeException; +import org.jvnet.hk2.component.Habitat; +import org.jvnet.hk2.config.ConfigBeanProxy; +import org.jvnet.hk2.config.Dom; + +/** + * @author Martin Mares + */ +public class ReferenceValidator implements ConstraintValidator { + + class RemoteKeyInfo { + final Method method; + final Class sourceClass; + final ReferenceConstraint.RemoteKey annotation; + public RemoteKeyInfo(Method method, Class sourceClass, ReferenceConstraint.RemoteKey annotation) { + this.method = method; + this.sourceClass = sourceClass; + this.annotation = annotation; + } + } + + static final Logger logger = LogDomains.getLogger(ConfigRefValidator.class, LogDomains.ADMIN_LOGGER); + static final LocalStringManagerImpl localStrings = new LocalStringManagerImpl(ReferenceValidator.class); + + private ReferenceConstraint rc; + + @Override + public void initialize(ReferenceConstraint rc) { + this.rc = rc; + } + + @Override + public boolean isValid(ConfigBeanProxy config, ConstraintValidatorContext cvc) throws UnexpectedTypeException { + if (config == null) { + return true; + } + Dom dom = Dom.unwrap(config); + if (rc.skipDuringCreation() && dom.getKey() == null) { + return true; //During creation the coresponding DOM is not fully loaded. + } + Collection remoteKeys = findRemoteKeys(config); + if (remoteKeys != null && !remoteKeys.isEmpty()) { + Habitat habitat = dom.getHabitat(); + boolean result = true; + boolean disableGlobalMessage = true; + for (RemoteKeyInfo remoteKeyInfo : remoteKeys) { + if (remoteKeyInfo.method.getParameterTypes().length > 0) { + throw new UnexpectedTypeException(localStrings.getLocalString("referenceValidator.not.getter", + "ReferenceConstraint annotaion must be on getter method only.")); + } + try { + Object value = remoteKeyInfo.method.invoke(config); + if (value instanceof String) { + String key = (String) value; + ConfigBeanProxy component = habitat.getComponent(remoteKeyInfo.annotation.type(), key); + if (component == null) { + result = false; + if (remoteKeyInfo.annotation.message().isEmpty()) { + disableGlobalMessage = false; + } else { + cvc.buildConstraintViolationWithTemplate(remoteKeyInfo.annotation.message()) + .addNode(Dom.convertName(remoteKeyInfo.method.getName())) + .addConstraintViolation(); + } + } + } else { + throw new UnexpectedTypeException(localStrings.getLocalString("referenceValidator.not.string", + "Remote key must be String.")); + } + } catch (Exception ex) { + return false; + } + } + if (!result && disableGlobalMessage) { + cvc.disableDefaultConstraintViolation(); + } + return result; + } + return true; + } + + private Collection findRemoteKeys(Object o) { + Collection result = new ArrayList(); + if (o == null) { + return result; + } + findRemoteKeys(o.getClass(), result); + return result; + } + + private void findRemoteKeys(Class c, Collection result) { + Method[] methods = c.getMethods(); + for (Method method : methods) { + ReferenceConstraint.RemoteKey annotation = method.getAnnotation(ReferenceConstraint.RemoteKey.class); + if (annotation != null) { + result.add(new RemoteKeyInfo(method, c, annotation)); + } + } + Class superclass = c.getSuperclass(); + if (superclass != null) { + findRemoteKeys(superclass, result); + } + Class[] interfaces = c.getInterfaces(); + for (Class iface : interfaces) { + findRemoteKeys(iface, result); + } + } + +} Index: src/main/java/com/sun/enterprise/config/serverbeans/JdbcResource.java =================================================================== --- src/main/java/com/sun/enterprise/config/serverbeans/JdbcResource.java (revision 51804) +++ src/main/java/com/sun/enterprise/config/serverbeans/JdbcResource.java (working copy) @@ -40,6 +40,7 @@ package com.sun.enterprise.config.serverbeans; +import com.sun.enterprise.config.serverbeans.customvalidators.ReferenceConstraint; import com.sun.enterprise.config.serverbeans.customvalidators.ResourcePoolReferenceConstraint; import org.jvnet.hk2.config.Attribute; import org.jvnet.hk2.config.ConfigBeanProxy; @@ -77,7 +78,8 @@ @RestRedirect(opType = RestRedirect.OpType.POST, commandName = "create-jdbc-resource"), @RestRedirect(opType = RestRedirect.OpType.DELETE, commandName = "delete-jdbc-resource") }) -@ResourcePoolReferenceConstraint(message="{resource.invalid}", payload=JdbcResource.class) +//@ResourcePoolReferenceConstraint(message="{resource.invalid}", payload=JdbcResource.class) +@ReferenceConstraint(skipDuringCreation=true, payload=JdbcResource.class) public interface JdbcResource extends ConfigBeanProxy, Injectable, Resource, PropertyBag, BindableResource, Payload { @@ -90,6 +92,7 @@ @Attribute @NotNull @Param(name="connectionpoolid") + @ReferenceConstraint.RemoteKey(message="{resourceref.invalid.poolname}", type=JdbcConnectionPool.class) String getPoolName(); /** Index: src/main/java/com/sun/enterprise/config/serverbeans/LocalStrings.properties =================================================================== --- src/main/java/com/sun/enterprise/config/serverbeans/LocalStrings.properties (revision 51804) +++ src/main/java/com/sun/enterprise/config/serverbeans/LocalStrings.properties (working copy) @@ -126,6 +126,7 @@ resourcepool.invalid.name.key=Invalid pool name. A pool name must start with a letter, number or underscore and may contain only letters, numbers and these special characters: hyphen, underscore, period, slash, semicolon, colon, hash. Pool name must not contain any spaces. resourcename.invalid.character=resource name cannot contain ':' character resource.invalid=Invalid pool name. Check whether the pool exists. +resourceref.invalid.poolname=Invalid pool name! Check whether the pool exists. SecureAdminPrincipalResolver.configTypeNotNamed=Config type {0} must extend {1} but does not SecureAdminPrincipalResolver.target_object_not_found=Cannot find a {0} with a name {1} Index: src/test/java/com/sun/enterprise/configapi/tests/validation/ReferenceConstrainTest.java =================================================================== --- src/test/java/com/sun/enterprise/configapi/tests/validation/ReferenceConstrainTest.java (revision 0) +++ src/test/java/com/sun/enterprise/configapi/tests/validation/ReferenceConstrainTest.java (working copy) @@ -0,0 +1,152 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ + +package com.sun.enterprise.configapi.tests.validation; + +import com.sun.enterprise.config.serverbeans.Domain; +import com.sun.enterprise.config.serverbeans.JdbcResource; +import com.sun.enterprise.configapi.tests.ConfigApiTest; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import javax.validation.ConstraintViolationException; +import org.junit.Test; +import org.junit.Before; +import org.jvnet.hk2.component.Habitat; +import org.glassfish.tests.utils.Utils; +import org.jvnet.hk2.config.ConfigBean; +import org.jvnet.hk2.config.ConfigSupport; +import org.jvnet.hk2.config.TransactionFailure; + +import static org.junit.Assert.*; + +/** + * + * @author mmares + */ +public class ReferenceConstrainTest extends ConfigApiTest { + +// private Logger logger = Logger.getLogger(ReferenceConstrainTest.class.getName()); + private Habitat habitat; + + @Override + public String getFileName() { + return "DomainTest"; + } + + @Override + public Habitat getHabitat() { + return habitat; + } + + private ConstraintViolationException findConstrViolation(Throwable thr) { + if (thr == null) { + return null; + } + if (thr instanceof ConstraintViolationException) { + return (ConstraintViolationException) thr; + } + return findConstrViolation(thr.getCause()); + } + + @Before + public void createNewHabitat() { + this.habitat = Utils.getNewHabitat(this); + } + + @Test + public void doChangeToValidPool() throws TransactionFailure { + Domain domain = habitat.getComponent(Domain.class); + //Find JdbcResource to chenge its values + Iterator iterator = domain.getResources().getResources(JdbcResource.class).iterator(); + JdbcResource jdbc = null; + while (iterator.hasNext()) { + JdbcResource res = iterator.next(); + if ("__TimerPool".equals(res.getPoolName())) { + jdbc = res; + break; + } + } + assertNotNull(jdbc); + ConfigBean poolConfig = (ConfigBean) ConfigBean.unwrap(jdbc); + Map> changes = new HashMap>(); + Map configChanges = new HashMap(); + configChanges.put("pool-name", "DerbyPool"); + changes.put(poolConfig, configChanges); + try { + ConfigSupport cs = getHabitat().getComponent(ConfigSupport.class); + cs.apply(changes); + assertTrue("Valid change, must reach this point", true); + } catch (TransactionFailure tf) { + //assertNotNull(findConstrViolation(tf)); + } + } + + @Test + public void doChangeToInValidPool() throws TransactionFailure { + Domain domain = habitat.getComponent(Domain.class); + //Find JdbcResource to chenge its values + Iterator iterator = domain.getResources().getResources(JdbcResource.class).iterator(); + JdbcResource jdbc = null; + while (iterator.hasNext()) { + JdbcResource res = iterator.next(); + if ("__TimerPool".equals(res.getPoolName())) { + jdbc = res; + break; + } + } + assertNotNull(jdbc); + ConfigBean poolConfig = (ConfigBean) ConfigBean.unwrap(jdbc); + Map> changes = new HashMap>(); + Map configChanges = new HashMap(); + configChanges.put("pool-name", "WrongPointer"); + changes.put(poolConfig, configChanges); + try { + ConfigSupport cs = getHabitat().getComponent(ConfigSupport.class); + cs.apply(changes); + assertFalse("Can not reach this point", true); + } catch (TransactionFailure tf) { + ConstraintViolationException cv = findConstrViolation(tf); +// cv.printStackTrace(System.out); + assertNotNull(cv); + } + } + +} \ No newline at end of file