org.jvnet.hk2.config.WriteableView Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2007-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 org.jvnet.hk2.config;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.lang.annotation.ElementType;
import java.text.MessageFormat;
import java.util.*;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import javax.validation.TraversableResolver;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.metadata.ConstraintDescriptor;
/**
* A WriteableView is a view of a ConfigBean object that allow access to the
* setters of the ConfigBean.
*
* @author Jerome Dochez
*/
public class WriteableView implements InvocationHandler, Transactor, ConfigView {
private final ConfigBean bean;
private final ConfigBeanProxy defaultView;
private final Map changedAttributes;
private final Map changedCollections;
Transaction currentTx;
private static Validator beanValidator=null;
private final static ResourceBundle i18n = ResourceBundle.getBundle("org.jvnet.hk2.config.LocalStrings");
public Transaction getTransaction() { return currentTx; }
public WriteableView(ConfigBeanProxy readView) {
this.bean = (ConfigBean) ((ConfigView) Proxy.getInvocationHandler(readView)).getMasterView();
this.defaultView = bean.createProxy();
changedAttributes = new HashMap();
changedCollections = new HashMap();
if (beanValidator == null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(null);
TraversableResolver traversableResolver =
new TraversableResolver() {
public boolean isReachable(Object traversableObject,
Path.Node traversableProperty, Class> rootBeanType,
Path pathToTraversableObject, ElementType elementType) {
return true;
}
public boolean isCascadable(Object traversableObject,
Path.Node traversableProperty, Class> rootBeanType,
Path pathToTraversableObject, ElementType elementType) {
return true;
}
};
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
ValidatorContext validatorContext = validatorFactory.usingContext();
validatorContext.messageInterpolator(new MessageInterpolatorImpl());
beanValidator = validatorContext.traversableResolver(
traversableResolver).getValidator();
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("hashCode"))
return super.hashCode();
if (method.getName().equals("equals"))
return super.equals(args[0]);
if(method.getAnnotation(DuckTyped.class)!=null) {
return bean.invokeDuckMethod(method,proxy,args);
}
ConfigModel.Property property = bean.model.toProperty(method);
if(property==null)
throw new IllegalArgumentException(
"No corresponding property found for method: "+method);
if(args==null || args.length==0) {
// getter, maybe one of our changed properties
if (changedAttributes.containsKey(property.xmlName())) {
// serve masked changes.
Object changedValue = changedAttributes.get(property.xmlName()).getNewValue();
if (changedValue instanceof Dom) {
return ((Dom) changedValue).createProxy();
} else {
return changedValue;
}
} else {
// pass through.
return getter(property, method.getGenericReturnType());
}
} else {
setter(property, args[0], method.getGenericParameterTypes()[0]);
return null;
}
}
public String getPropertyValue(String propertyName) {
ConfigModel.Property prop = this.getProperty(propertyName);
if (prop!=null) {
if (changedAttributes.containsKey(prop.xmlName())) {
// serve masked changes.
return (String) changedAttributes.get(prop.xmlName()).getNewValue();
} else {
return (String) getter(prop, String.class);
}
}
return null;
}
public Object getter(ConfigModel.Property property, java.lang.reflect.Type t) {
Object value = bean._getter(property, t);
if (value instanceof List) {
if (!changedCollections.containsKey(property.xmlName())) {
// wrap collections so we can record events on that collection mutation.
changedCollections.put(property.xmlName(),
new ProtectedList(List.class.cast(value), defaultView, property.xmlName()));
}
return changedCollections.get(property.xmlName());
}
return value;
}
public void setter(ConfigModel.Property property,
Object newValue, java.lang.reflect.Type t) {
// are we still in a transaction
if (currentTx==null) {
throw new IllegalStateException("Not part of a transaction");
}
try {
if (newValue != null)
handleValidation(property, newValue);
} catch(Exception v) {
bean.getLock().unlock();
throw new RuntimeException(v);
}
// Following is a check to avoid duplication of elements with same key
// attribute values. See Issue 7956
if (property instanceof ConfigModel.AttributeLeaf) {
// First check if the key leaf attribute is being set
ConfigModel.AttributeLeaf al =
(ConfigModel.AttributeLeaf)property;
ConfigBean master = getMasterView();
String key = master.model.key;
// A key attribute may not exist at all if none of the attribs of
// an element are annotated with key=true. If one exists, make sure
// that attribute is actually the one being set
if ((key != null) && (key.substring(1).equals(property.xmlName))) {
// remove leading @
key = key.substring(1);
// Extract the old key value
String oldKeyValue = getPropertyValue(key);
// Get the Parent Element which has the key attribute specified
// through the input paramater 'property'. For e.g. in case of
// TopLevel->Resources->ConnectorConnectionPool->name(key attrib)
// thisview will equal ConnectorConnectionPool
Dom thisview = Dom.unwrap(defaultView);
// parent will equal Resources
Dom parent = thisview.parent();
// siblings will contain all ConnectorConnectionPools under
// Resources
List siblings =
parent.domNodeByTypeElements(thisview.getProxyType());
// Iterate through each sibling element and see if anyone has
// same key. If true throw an exception after unlocking this
// element
for (Dom sibling : siblings) {
String siblingKey = sibling.getKey();
if (newValue.equals(siblingKey)) {
bean.getLock().unlock();
throw new IllegalArgumentException(
"Keys cannot be duplicate. Old value of this key " +
"property, " + oldKeyValue + "will be retained");
}
}
}
}
// setter
Object oldValue = bean.getter(property, t);
if (newValue instanceof ConfigBeanProxy) {
ConfigView bean = (ConfigView)
Proxy.getInvocationHandler((ConfigBeanProxy) newValue);
newValue = bean.getMasterView();
}
PropertyChangeEvent evt = new PropertyChangeEvent(
defaultView,property.xmlName(), oldValue, newValue);
try {
for (ConfigBeanInterceptor interceptor : bean.getOptionalFeatures()) {
interceptor.beforeChange(evt);
}
} catch(PropertyVetoException e) {
throw new RuntimeException(e);
}
changedAttributes.put(property.xmlName(), evt);
for (ConfigBeanInterceptor interceptor : bean.getOptionalFeatures()) {
interceptor.afterChange(evt, System.currentTimeMillis());
}
}
public ConfigModel.Property getProperty(String xmlName) {
return bean.model.findIgnoreCase(xmlName);
}
/**
* Enter a new Transaction, this method should return false if this object
* is already enlisted in another transaction, or cannot be enlisted with
* the passed transaction. If the object returns true, the object
* is enlisted in the passed transaction and cannot be enlisted in another
* transaction until either commit or abort has been issued.
*
* @param t the transaction to enlist with
* @return true if the enlisting with the passed transaction was accepted,
* false otherwise
*/
public synchronized boolean join(Transaction t) {
if (currentTx==null) {
currentTx = t;
t.addParticipant(this);
return true;
}
return false;
}
/**
* Returns true of this Transaction can be committed on this object
*
* @param t is the transaction to commit, should be the same as the
* one passed during the join(Transaction t) call.
* @return true if the trsaction commiting would be successful
*/
public synchronized boolean canCommit(Transaction t) throws TransactionFailure {
Set constraintViolations =
beanValidator.validate(this.getProxy(this.getProxyType()));
try {
handleValidationException(constraintViolations);
} catch (ConstraintViolationException constraintViolationException) {
throw new TransactionFailure(constraintViolationException.getMessage(), constraintViolationException);
}
return currentTx==t;
}
private void handleValidationException(Set constraintViolations) throws ConstraintViolationException {
if (constraintViolations != null && !constraintViolations.isEmpty()) {
Iterator> it = constraintViolations.iterator();
StringBuilder sb = new StringBuilder();
sb.append(MessageFormat.format(i18n.getString("bean.validation.failure"), this.getProxyType().getSimpleName()));
String violationMsg = i18n.getString("bean.validation.constraintViolation");
while (it.hasNext()) {
ConstraintViolation cv = it.next();
sb.append(" ");
sb.append(MessageFormat.format(violationMsg, cv.getMessage(), cv.getPropertyPath()));
if (it.hasNext()) {
sb.append(i18n.getString("bean.validation.separator"));
}
}
bean.getLock().unlock();
throw new ConstraintViolationException(sb.toString(), constraintViolations);
}
}
/** remove @ or <> eg "@foo" => "foo" or "" => "foo" */
public static String stripMarkers(final String s ) {
if ( s.startsWith("@") ) {
return s.substring(1);
}
else if ( s.startsWith("<") ) {
return s.substring(1, s.length()-1);
}
return s;
}
/**
* Commit this Transaction.
*
* @param t the transaction commiting.
* @throws TransactionFailure
* if the transaction commit failed
*/
public synchronized List commit(Transaction t) throws TransactionFailure {
if (currentTx==t) {
currentTx=null;
}
// a key attribute must be non-null and have length >= 1
final ConfigBean master = getMasterView();
final String keyStr = master.model.key;
if ( keyStr != null) {
final String key = stripMarkers(keyStr);
final String value = getPropertyValue(key);
if ( value == null ) {
throw new TransactionFailure( "Key value cannot be null: " + key );
}
if ( value.length() == 0 ) {
throw new TransactionFailure( "Key value cannot be empty string: " + key );
}
}
try {
List appliedChanges = new ArrayList();
for (PropertyChangeEvent event : changedAttributes.values()) {
ConfigModel.Property property = bean.model.findIgnoreCase(event.getPropertyName());
ConfigBeanInterceptor interceptor = bean.getOptionalFeature(ConfigBeanInterceptor.class);
try {
if (interceptor!=null) {
interceptor.beforeChange(event);
}
} catch (PropertyVetoException e) {
throw new TransactionFailure(e.getMessage(), e);
}
property.set(bean, event.getNewValue());
if (interceptor!=null) {
interceptor.afterChange(event, System.currentTimeMillis());
}
appliedChanges.add(event);
}
for (ProtectedList entry : changedCollections.values()) {
List © 2015 - 2025 Weber Informatics LLC | Privacy Policy