/*=============================================================================*
* Copyright 2006 The Apache Software Foundation
*
* 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.apache.muse.ws.resource.properties.impl;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.apache.muse.util.MultiMap;
import org.apache.muse.util.messages.Messages;
import org.apache.muse.util.messages.MessagesFactory;
import org.apache.muse.util.xml.XmlUtils;
import org.apache.muse.ws.resource.WsResourceCapability;
import org.apache.muse.ws.resource.basefaults.BaseFault;
import org.apache.muse.ws.resource.metadata.MetadataDescriptor;
import org.apache.muse.ws.resource.metadata.faults.MetadataValidationFault;
import org.apache.muse.ws.resource.metadata.impl.ExternalChangeApprover;
import org.apache.muse.ws.resource.metadata.impl.InsertOnlyApprover;
import org.apache.muse.ws.resource.metadata.impl.ReadOnlyApprover;
import org.apache.muse.ws.resource.metadata.impl.StaticValuesApprover;
import org.apache.muse.ws.resource.metadata.impl.ValidValuesApprover;
import org.apache.muse.ws.resource.properties.ResourcePropertyCollection;
import org.apache.muse.ws.resource.properties.get.faults.InvalidResourcePropertyQNameFault;
import org.apache.muse.ws.resource.properties.listeners.PropertyChangeApprover;
import org.apache.muse.ws.resource.properties.listeners.PropertyChangeListener;
import org.apache.muse.ws.resource.properties.listeners.PropertyReadListener;
import org.apache.muse.ws.resource.properties.schema.ResourcePropertiesSchema;
import org.apache.muse.ws.resource.properties.schema.faults.SchemaValidationFault;
import org.apache.muse.ws.resource.properties.set.SetRequest;
import org.apache.muse.ws.resource.properties.set.SetRequestComponent;
import org.apache.muse.ws.resource.properties.set.faults.UnableToModifyResourcePropertyFault;
import org.apache.muse.ws.resource.properties.set.faults.UnableToPutResourcePropertyDocumentFault;
import org.apache.muse.ws.resource.properties.set.impl.InsertRequest;
import org.apache.muse.ws.resource.properties.set.impl.SimpleSetRequest;
import org.apache.muse.ws.resource.properties.set.impl.UpdateRequest;
/**
*
* SimpleResourcePropertyCollection is Muse's default implementation of the
* WS-RF state model. It delegates the handling of actual WS-RP methods to
* capabilities that define the resource properties, serving only to enforce
* WS-RP schema and metadata constraints and the formatting of WS-RP faults.
* It also provides the implementation of the listener framework that allows
* other components to hook into various parts of the WS-RP operations.
*
* @author Dan Jemiolo (danj)
*
*/
public class SimpleResourcePropertyCollection implements ResourcePropertyCollection
{
//
// Used to lookup all exception messages
//
private static Messages _MESSAGES =
MessagesFactory.get(SimpleResourcePropertyCollection.class);
//
// All PropertyChangeApprovers by property name
//
private MultiMap _approversByQName = new MultiMap();
//
// All PropertyChangeListeners by property name
//
private MultiMap _listenersByQName = new MultiMap();
//
// The RMD file that holds the metadata for these properties.
//
private MetadataDescriptor _metadata = null;
//
// All PropertyReadListeners by property name
//
private MultiMap _readersByQName = new MultiMap();
//
// The schema that describes the property types and their constraints.
//
private ResourcePropertiesSchema _schema = null;
//
// The security token allows other components in the application to
// modify read-only properties that are not accessible by clients.
// Properties that are read-only may still be mutable (or appendable),
// which means that they may change, but cannot be explicitly modified
// by consumers.
//
// Our security token is just a unique reference (which cannot be
// duplicated within the JVM).
//
private Object _securityToken = new Object();
public synchronized void addCapability(WsResourceCapability capability)
{
QName[] names = capability.getPropertyNames();
ResourcePropertiesSchema schema = getSchema();
for (int n = 0; n < names.length; ++n)
schema.setCapability(names[n], capability);
}
public synchronized final void addChangeApprover(PropertyChangeApprover approver)
{
if (approver == null)
throw new NullPointerException(_MESSAGES.get("NullPCA"));
QName qname = approver.getPropertyName();
if (!hasPropertyDefinition(qname))
{
Object[] filler = { qname };
throw new IllegalArgumentException(_MESSAGES.get("PropertyNotInSchema", filler));
}
_approversByQName.put(qname, approver);
}
public synchronized final void addChangeListener(PropertyChangeListener listener)
{
if (listener == null)
throw new NullPointerException(_MESSAGES.get("NullPCL"));
QName qname = listener.getPropertyName();
if (!hasPropertyDefinition(qname))
{
Object[] filler = { qname };
throw new IllegalArgumentException(_MESSAGES.get("PropertyNotInSchema", filler));
}
_listenersByQName.put(qname, listener);
}
protected void addInitialValues(QName qname)
{
Collection initialValues = getMetadata().getInitialValues(qname);
Iterator i = initialValues.iterator();
//
// insert all the initial values (if any)
//
try
{
while (i.hasNext())
insertResourceProperty(qname, new Object[]{ i.next() });
}
//
// if we get an error inserting a "required" value,
// something is totally awry - throw unchecked
//
catch (BaseFault fault)
{
throw new RuntimeException(fault.getMessage(), fault);
}
}
protected void addPermissions(QName qname)
{
MetadataDescriptor metadata = getMetadata();
//
// make sure read-only properties aren't modified from the
// outside (can still be mutable inside)
//
boolean readOnly = metadata.isReadOnlyExternal(qname);
PropertyChangeApprover security = new ExternalChangeApprover(qname, readOnly);
security.setSecurityToken(getSecurityToken());
addChangeApprover(security);
//
// if we can't update, we're either appendable (insert only)
// or constant (read only). we need an enforcer!
//
if (!metadata.canUpdate(qname))
{
PropertyChangeApprover approver = null;
//
// insert only
//
if (metadata.canInsert(qname))
approver = new InsertOnlyApprover(qname);
//
// read only
//
else
approver = new ReadOnlyApprover(qname);
addChangeApprover(approver);
}
}
public synchronized final void addReadListener(PropertyReadListener listener)
{
if (listener == null)
throw new NullPointerException(_MESSAGES.get("NullPRL"));
QName qname = listener.getPropertyName();
if (!hasPropertyDefinition(qname))
{
Object[] filler = { qname };
throw new IllegalArgumentException(_MESSAGES.get("PropertyNotInSchema", filler));
}
_readersByQName.put(qname, listener);
}
protected void addStaticValues(QName qname)
{
Collection staticValues = getMetadata().getStaticValues(qname);
//
// make sure instances that are StaticValues aren't deleted
//
if (!staticValues.isEmpty())
{
PropertyChangeApprover staticValuesListener =
new StaticValuesApprover(qname, _metadata);
addChangeApprover(staticValuesListener);
Iterator i = staticValues.iterator();
//
// insert all the static (required) values
//
try
{
while (i.hasNext())
insertResourceProperty(qname, new Object[]{ i.next() });
}
//
// if we get an error inserting a "required" value,
// something is totally awry - throw unchecked
//
catch (BaseFault fault)
{
throw new RuntimeException(fault.getMessage(), fault);
}
}
}
protected void addValidValues(QName qname)
{
//
// make sure only ValidValues are inserted (if any)
//
// NOTE: this does NOT handle ValidValueRange. the user will
// need to provide a PCA for that, because it is highly-
// dependent on the semantics of the property type.
//
addChangeApprover(new ValidValuesApprover(qname, getMetadata()));
}
public synchronized void applyMetadata()
{
if (getMetadata() == null)
throw new IllegalStateException(_MESSAGES.get("NoMetadata"));
Iterator i = getMetadata().getPropertyNames().iterator();
//
// for each property in the RMD, read its constraints and add
// approvers that enforce those constraints
//
while (i.hasNext())
{
QName qname = (QName)i.next();
addInitialValues(qname);
addStaticValues(qname);
addValidValues(qname);
addPermissions(qname);
}
}
/**
*
* Reports the completed property change to all PropertyChangeListeners.
* If one of the listeners fails and throws an exception, the method is
* over - it will not report the change to all remaining listeners
*
* @param qname
* @param oldValue
* @param newValue
*
* @throws BaseFault
* <ul>
* <li>If one of the listeners fails while responding to the
* change; this does not indicate that the change was invalid,
* just that the listener's reaction failed.</li>
* </ul>
*
*/
protected final void changeCompleted(QName qname, Element oldValue, Element newValue)
throws BaseFault
{
if (qname == null)
throw new NullPointerException(_MESSAGES.get("NullQName"));
Collection listeners = (Collection)_listenersByQName.get(qname);
if (listeners != null)
{
Iterator i = listeners.iterator();
while (i.hasNext())
{
PropertyChangeListener listener = (PropertyChangeListener)i.next();
listener.propertyChanged(oldValue, newValue);
}
}
}
/**
*
* Reports the pending property change to all PropertyChangeApprovers.
* As soon as one approver cancels the change by throwing an exception,
* the method is over - it does not report to all approvers once one
* has objected.
*
* @param qname
* @param oldValue
* @param newValue
* @param securityToken
*
* @throws BaseFault
* <ul>
* <li>If one of the approvers does not approve!</li>
* </ul>
*
*/
protected final void changeRequested(QName qname,
Element oldValue,
Element newValue,
Object securityToken)
throws BaseFault
{
if (qname == null)
throw new NullPointerException(_MESSAGES.get("NullQName"));
Collection approvers = (Collection)_approversByQName.get(qname);
if (approvers != null)
{
Iterator i = approvers.iterator();
while (i.hasNext())
{
PropertyChangeApprover approver =
(PropertyChangeApprover)i.next();
approver.validateChange(oldValue, newValue, securityToken);
}
}
}
protected SetRequest createInsertRequests(Element newDoc)
throws UnableToPutResourcePropertyDocumentFault, BaseFault
{
//
// Process each top level property from root of doc
//
Element[] properties = XmlUtils.getAllElements(newDoc);
SetRequest set = new SimpleSetRequest();
for (int i = 0; i < properties.length; i++)
{
QName propQName = XmlUtils.getElementQName(properties[i]);
if (getMetadata().isReadOnlyExternal(propQName))
{
Object[] filler = { propQName };
throw new UnableToPutResourcePropertyDocumentFault(_MESSAGES.get("PutRPDocReadOnlyError", filler));
}
SetRequestComponent insertComp = new InsertRequest(propQName, properties[i]);
set.addRequestComponent(insertComp);
}
return set;
}
protected void deleteMutableProperties()
throws BaseFault
{
Iterator i = getPropertyNames().iterator();
MetadataDescriptor metadata = getMetadata();
while (i.hasNext())
{
QName propName = (QName)i.next();
if (metadata.canDelete(propName) && !metadata.isReadOnlyExternal(propName))
{
//
// check if there are any instances to delete
//
Element[] instances = getResourceProperty(propName);
if (instances.length > 0)
deleteResourceProperty(propName, getSecurityToken());
}
}
}
public void deleteResourceProperty(QName qname)
throws BaseFault
{
deleteResourceProperty(qname, getSecurityToken());
}
public synchronized void deleteResourceProperty(QName qname, Object securityToken)
throws BaseFault
{
if (qname == null)
throw new NullPointerException(_MESSAGES.get("NullQName"));
//
// find all properties with the given name...
//
Element[] results = getResourceProperty(qname);
if (results.length == 0)
{
Object[] filler = { qname };
throw new UnableToModifyResourcePropertyFault(_MESSAGES.get("NoInstancesToDelete", filler));
}
//
// make sure deletion is okay from a SCHEMA point-of-view. this
// does not validate deletion from a read-only/immutable. that
// is done by PCAs
//
validateDelete(qname, results.length, results.length);
//
// inform approvers of the pending deletion - this could
// throw and cancel the deletion
//
for (int n = 0; n < results.length; ++n)
changeRequested(qname, results[n], null, securityToken);
WsResourceCapability capability = getCapability(qname);
capability.deleteProperty(qname);
//
// inform listeners of deletion
//
for (int n = 0; n < results.length; ++n)
changeCompleted(qname, results[n], null);
}
public WsResourceCapability getCapability(QName qname)
{
return getSchema().getCapability(qname);
}
public synchronized final Iterator getChangeApprovers(QName property)
{
Collection approvers = (Collection)_approversByQName.get(property);
return approvers.iterator();
}
public synchronized final Iterator getChangeListeners(QName property)
{
Collection listeners = (Collection)_listenersByQName.get(property);
return listeners.iterator();
}
public synchronized final MetadataDescriptor getMetadata()
{
return _metadata;
}
/**
*
* {@inheritDoc}
* <br><br>
* This method returns the actual Elements that are stored in the
* underlying DOM Document - modifying them will modify the WS-RP
* document. Be careful!
*
*/
public synchronized Element[] getMultipleResourceProperties(QName[] qnames)
throws InvalidResourcePropertyQNameFault, BaseFault
{
if (qnames == null)
throw new NullPointerException(_MESSAGES.get("NullQNameArray"));
//
// first get all instances of each property named...
//
Element[][] results = new Element[qnames.length][];
int total = 0;
for (int n = 0; n < qnames.length; ++n)
{
results[n] = getResourceProperty(qnames[n]);
total += results[n].length;
}
//
// ...then copy them into one array to return
//
Element[] oneBigArray = new Element[total];
//
// all property instances are copied in order, so all instances
// of the same name are together
//
for (int n = 0, current = 0; n < results.length; ++n)
{
System.arraycopy(results[n], 0, oneBigArray, current, results[n].length);
current += results[n].length;
}
return oneBigArray;
}
/**
*
* {@inheritDoc}
* <br><br>
* Unlike getResourceProperty(QName), the values returned from this
* method have been transformed from Elements to POJOs and do <b>not</b>
* represent the values in the underlying DOM Document. The implementation
* uses the Serializers registered with Muse to parse the properties.
*
* @see #getResourceProperty(QName)
*
* @see WsrpUtils#convertToObjects(Element[], Class)
*
*/
public synchronized Object getPropertyAsObject(QName qname, Class type)
throws BaseFault
{
Element[] properties = getResourceProperty(qname);
return WsrpUtils.convertToObjects(properties, type);
}
public synchronized Collection getPropertyNames()
{
return getSchema().getPropertyNames();
}
public synchronized final Iterator getReadListeners(QName property)
{
Collection readers = (Collection)_readersByQName.get(property);
return readers.iterator();
}
/**
*
* {@inheritDoc}
* <br><br>
* This method returns the actual Elements that are stored in the
* underlying DOM Document - modifying them will modify the WS-RP
* document. Be careful!
* <br><br>
* Users who want more type safety and better encapsulation should
* use getPropertyAsObject(QName, Class).
*
* @see #getPropertyAsObject(QName, Class)
*
*/
public synchronized Element[] getResourceProperty(QName qname)
throws InvalidResourcePropertyQNameFault, BaseFault
{
if (qname == null)
throw new NullPointerException(_MESSAGES.get("NullQName"));
//
// it's invalid to read a property whose type is undefined
//
// NOTE: this is DIFFERENT from reading a property whose
// type is undefined but currently has zero instances
//
if (!hasPropertyDefinition(qname))
{
Object[] filler = { qname };
throw new InvalidResourcePropertyQNameFault(_MESSAGES.get("PropertyNotInSchema", filler));
}
WsResourceCapability capability = getCapability(qname);
Element[] results = capability.getProperty(qname);
//
// let listeners/updaters know of the read request - the first
// listener will have no values to work with
//
return readRequested(qname, results);
}
public synchronized Element getResourcePropertyDocument()
throws BaseFault
{
Document doc = XmlUtils.createDocument();
QName rootName = getSchema().getElementName();
Element root = XmlUtils.createElement(doc, rootName);
doc.appendChild(root);
Collection names = getPropertyNames();
QName[] namesAsArray = new QName[names.size()];
namesAsArray = (QName[])names.toArray(namesAsArray);
Element[] values = getMultipleResourceProperties(namesAsArray);
for (int n = 0; n < values.length; ++n)
{
values[n] = (Element)doc.importNode(values[n], true);
root.appendChild(values[n]);
}
return root;
}
public synchronized final ResourcePropertiesSchema getSchema()
{
return _schema;
}
public Object getSecurityToken()
{
return _securityToken;
}
public boolean hasPropertyDefinition(QName qname)
{
if (_schema == null)
throw new IllegalStateException(_MESSAGES.get("NoSchema"));
return _schema.hasProperty(qname);
}
public synchronized void insertOrUpdate(QName property, Object value)
throws BaseFault
{
insertOrUpdate(property, new Object[]{ value });
}
public synchronized void insertOrUpdate(QName property, Object[] values)
throws BaseFault
{
if (getResourceProperty(property).length == 0)
insertResourceProperty(property, values);
else
updateResourceProperty(property, values);
}
public synchronized void insertResourceProperty(QName qname, Object[] values)
throws BaseFault
{
insertResourceProperty(qname, values, getSecurityToken());
}
public synchronized void insertResourceProperty(QName qname,
Object[] values,
Object securityToken)
throws BaseFault
{
if (qname == null)
throw new NullPointerException(_MESSAGES.get("NullQName"));
if (values == null)
throw new NullPointerException(_MESSAGES.get("NullValuesArray"));
if (values.length == 0)
throw new IllegalArgumentException(_MESSAGES.get("EmptyValuesArray"));
//
// find all current instances of the property...
//
Element[] current = getResourceProperty(qname);
InsertRequest request = new InsertRequest(qname, values);
Element[] valuesXML = request.getValues();
//
// make sure that the insert is valid according to the SCHEMA
//
validateInsert(qname, current.length, valuesXML);
for (int n = 0; n < valuesXML.length; ++n)
changeRequested(qname, null, valuesXML[n], securityToken);
WsResourceCapability capability = getCapability(qname);
capability.insertProperty(qname, valuesXML);
for (int n = 0; n < valuesXML.length; ++n)
changeCompleted(qname, null, valuesXML[n]);
}
public synchronized Element putResourcePropertyDocument(Element newDoc)
throws UnableToPutResourcePropertyDocumentFault, BaseFault
{
if( newDoc == null)
throw new NullPointerException(_MESSAGES.get("NullRPDoc"));
try
{
//
// entire rp doc is replaced by this op, therefore all properties,
// except read-only properties, are deleted first
//
deleteMutableProperties();
//
// break the operation down into multiple set request insert components
//
SetRequest insertRequests = createInsertRequests(newDoc);
setResourceProperties(insertRequests);
}
catch (BaseFault fault)
{
throw new UnableToPutResourcePropertyDocumentFault(_MESSAGES.get("PutRPDocError", new Object[] {fault.getMessage()}));
}
//
// if final representation and intended representation are not the same,
// return the document on response
//
Element finalDoc = getResourcePropertyDocument();
//
// strip all white space text nodes directly under root of new doc before doing comparison, as
// in WS-RP we don't care about white space under the root.
//
Element wsFreeNewDoc = stripWhiteSpaceChildren(newDoc);
return XmlUtils.equals(wsFreeNewDoc, finalDoc) ? null : finalDoc;
}
/**
*
* Reports all property read requests to the PropertyReadListeners. If
* one of the listeners fails and throws an exception, the method is
* over - it will not report to all remaining listeners once one has
* failed. Listeners will receive the actual values that will be given
* to the caller, so they can modify them if desired.
*
* @param qname
* The name of the property being read.
*
* @param properties
* The current values of the property, which will be given to
* the caller once all listeners have been invoked.
*
* @throws BaseFault
* <ul>
* <li>If one of the listeners fails.</li>
* </ul>
*
*/
protected final Element[] readRequested(QName qname, Element[] properties)
throws BaseFault
{
if (qname == null)
throw new NullPointerException(_MESSAGES.get("NullQName"));
Collection readers = (Collection)_readersByQName.get(qname);
if (readers != null)
{
Iterator i = readers.iterator();
while (i.hasNext())
{
PropertyReadListener reader = (PropertyReadListener)i.next();
properties = reader.readRequested(properties);
}
}
return properties;
}
public synchronized final void removeChangeApprover(PropertyChangeApprover approver)
{
if (approver == null)
throw new NullPointerException(_MESSAGES.get("NullPCA"));
removeItem(_approversByQName, approver.getPropertyName(), approver);
}
public synchronized final void removeChangeListener(PropertyChangeListener listener)
{
if (listener == null)
throw new NullPointerException(_MESSAGES.get("NullPCL"));
removeItem(_listenersByQName, listener.getPropertyName(), listener);
}
/**
*
* Removes the given value from the collection of values associated with
* the given key. If removing the value results in the key having no
* more entries associated with it, the key itself is removed.
*
* @param map
* @param key
* @param value
*
*/
private void removeItem(MultiMap map, Object key, Object value)
{
Collection all = (Collection)map.get(key);
all.remove(value);
if (all.isEmpty())
map.remove(key);
}
public synchronized final void removeReadListener(PropertyReadListener listener)
{
if (listener == null)
throw new NullPointerException(_MESSAGES.get("NullPRL"));
removeItem(_readersByQName, listener.getPropertyName(), listener);
}
public synchronized void setMetadata(MetadataDescriptor metadata)
{
if (metadata == null)
throw new NullPointerException(_MESSAGES.get("NullMetadataDescriptor"));
_metadata = metadata;
}
public synchronized void setResourceProperties(SetRequest request)
throws BaseFault
{
List ops = request.getRequestComponents();
//
// we must have at least one set operation
//
if (ops.isEmpty())
throw new IllegalArgumentException(_MESSAGES.get("EmptySetRequest"));
//
// execute each operation in order
//
// NOTE: This is not a transaction system, so if one operation
// fails in the middle, the operations that have been
// performed to that point are not rolled back. This is
// consistent with the WS-RP spec but may not be adequate
// for building manageable resources.
//
Iterator i = ops.iterator();
while (i.hasNext())
{
SetRequestComponent next = (SetRequestComponent)i.next();
next.execute(this);
}
}
public synchronized void setSchema(ResourcePropertiesSchema schema)
{
if (schema == null)
throw new NullPointerException(_MESSAGES.get("NullSchema"));
_schema = schema;
}
/**
*
* Given an element, removes all direct white space children nodes
*
*/
private Element stripWhiteSpaceChildren(Element root)
{
NodeList children = root.getChildNodes();
for (int i = 0; i < children.getLength(); i++)
{
Node child = children.item(i);
if (child.getNodeType() == Node.TEXT_NODE && child.getNodeValue().trim().equals(""))
child.getParentNode().removeChild(child);
}
return root;
}
/**
*
* @return An XML representation of the current WS-RP document, without
* the XML header.
*
*/
public synchronized String toString()
{
return XmlUtils.toString(toXML(), false);
}
public synchronized Element toXML()
{
try
{
return getResourcePropertyDocument();
}
catch (BaseFault error)
{
throw new RuntimeException(error.getMessage(), error);
}
}
/**
*
* @return A <b>copy</b> of the underlying DOM Document that is used to
* store the properties.
*
*/
public synchronized Element toXML(Document factory)
{
try
{
Element wsrp = getResourcePropertyDocument();
return (Element)factory.importNode(wsrp, true);
}
catch (BaseFault error)
{
throw new RuntimeException(error.getMessage(), error);
}
}
public synchronized void updateResourceProperty(QName qname, Object[] values)
throws BaseFault
{
updateResourceProperty(qname, values, getSecurityToken());
}
public synchronized void updateResourceProperty(QName qname,
Object[] values,
Object securityToken)
throws BaseFault
{
if (qname == null)
throw new NullPointerException(_MESSAGES.get("NullQName"));
if (values == null)
throw new NullPointerException(_MESSAGES.get("NullValuesArray"));
//
// get all current instances of the property
//
Element[] current = getResourceProperty(qname);
//
// we can't update if there are no instances of the property.
// callers must insert, THEN update
//
if (current.length == 0)
{
Object[] filler = { qname };
throw new UnableToModifyResourcePropertyFault(_MESSAGES.get("PropertyNotFound", filler));
}
UpdateRequest request = new UpdateRequest(qname, values);
Element[] valuesXML = request.getValues();
//
// make sure the update is valid according to the SCHEMA - this
// test assumes we will remove all current values and insert
// new ones
//
validateInsert(qname, 0, valuesXML);
int lcd = Math.min(current.length, valuesXML.length);
for (int n = 0; n < lcd; ++n)
changeRequested(qname, current[n], valuesXML[n], securityToken);
WsResourceCapability capability = getCapability(qname);
capability.updateProperty(qname, valuesXML);
for (int n = 0; n < lcd; ++n)
changeCompleted(qname, current[n], valuesXML[n]);
//
// if there were more copies before the update than after,
// we need to send deletion notifications about the extras
//
for (int n = lcd; n < current.length; ++n)
changeCompleted(qname, current[n], null);
//
// the other possibility is that there are more copies after
// the update than before, so we still have some values to
// report that are not "replacements"
//
for (int n = lcd; n < valuesXML.length; ++n)
changeCompleted(qname, null, valuesXML[n]);
}
/**
*
* Confirms that deleting the desired number of property instances will
* result in a WS-RP document that is valid according to the schema.
*
* @param qname
* The name of the property whose instances are being deleted.
*
* @param currentSize
* The current number of instances (before the delete).
*
* @param toDelete
* The number of instances being deleted.
*
* @throws BaseFault
* <ul>
* <li>If the property name is undefined.</li>
* <li>If deleting the instances would put the property below
* its minimum value ("minOccurs").</li>
* </ul>
*
*/
protected void validateDelete(QName qname, int currentSize, int toDelete)
throws BaseFault
{
//
// make sure that deleting 'toDelete' instances won't put us
// below the minimum
//
int min = _schema.getMinOccurs(qname);
if (currentSize - toDelete < min)
{
Object[] filler = {
qname, new Integer(min), new Integer(currentSize - toDelete)
};
throw new SchemaValidationFault(_MESSAGES.get("BelowMinimumPotential", filler));
}
}
/**
*
* Confirms that inserting the desired property instances will result in
* a WS-RP document that is valid according to the schema.
*
* @param qname
* The name of the property that is being inserted.
*
* @param currentSize
* The current number of instances (before the insertion).
*
* @param values
* The property values to insert into the document.
*
* @throws BaseFault
* <ul>
* <li>If the property name is undefined.</li>
* <li>If inserting the instances would put the property above
* its maximum value ("maxOccurs").</li>
* <li>If any of the values is null, and the property is not
* nillable.</li>
* </ul>
*
*/
protected void validateInsert(QName qname, int currentSize, Element[] values)
throws BaseFault
{
ResourcePropertiesSchema schema = getSchema();
//
// make sure that adding value.length instances will not put us
// above the maximum (if any)
//
int max = schema.getMaxOccurs(qname);
int total = currentSize + values.length;
if (!_schema.isMaxUnbounded(qname) && total > max)
{
Object[] filler = { qname, new Integer(max), new Integer(total) };
throw new SchemaValidationFault(_MESSAGES.get("AboveMaximumPotential", filler));
}
//
// are we trying to set to null when we shouldn't be?
//
boolean isNillable = schema.isNillable(qname);
for (int n = 0; n < values.length; ++n)
{
if (!isNillable && !values[n].hasChildNodes())
{
Object[] filler = { qname };
throw new SchemaValidationFault(_MESSAGES.get("NotNillable", filler));
}
}
}
public synchronized void validateMetadata()
throws BaseFault
{
if (_metadata == null)
throw new IllegalStateException(_MESSAGES.get("NoMetadata"));
Iterator i = _metadata.getPropertyNames().iterator();
//
// for each wsrmd:Property, make sure it's in the schema and
// that all current values are valid
//
while (i.hasNext())
{
QName qname = (QName)i.next();
Element[] current = getResourceProperty(qname);
//
// no instances OR the RMD does not have any metadata - skip
//
if (current.length == 0 || !_metadata.hasProperty(qname))
continue;
//
// validate current values
//
for (int n = 0; n < current.length; ++n)
{
if (!_metadata.isValidValue(qname, current[n]))
{
Object[] filler = { qname, XmlUtils.toString(current[n], false) };
throw new MetadataValidationFault(_MESSAGES.get("InvalidValue", filler));
}
}
}
}
public synchronized void validateSchema()
throws BaseFault
{
//
// for every property in the schema, check the size constraints
//
ResourcePropertiesSchema schema = getSchema();
Iterator i = schema.getPropertyNames().iterator();
while (i.hasNext())
{
QName qname = (QName)i.next();
if (!schema.hasCapability(qname))
{
Object[] filler = { qname };
throw new IllegalStateException(_MESSAGES.get("NoCapabilityForProperty", filler));
}
int max = schema.getMaxOccurs(qname);
boolean maxUnbounded = schema.isMaxUnbounded(qname);
int min = schema.getMinOccurs(qname);
boolean isNillable = schema.isNillable(qname);
//
// get all current instances
//
Element[] instances = getResourceProperty(qname);
//
// minimum number of instances?
//
if (instances.length < min)
{
Object[] filler = {
qname, new Integer(min), new Integer(instances.length)
};
throw new SchemaValidationFault(_MESSAGES.get("BelowMinimum", filler));
}
//
// under the max?
//
if (!maxUnbounded && instances.length > max)
{
Object[] filler = {
qname, new Integer(min), new Integer(instances.length)
};
throw new SchemaValidationFault(_MESSAGES.get("AboveMaximum", filler));
}
//
// are any instances null that shouldn't be?
//
String message = _MESSAGES.get("NotNillable", new Object[]{ qname });
if (!isNillable)
{
for (int n = 0; n < instances.length; ++n)
{
//
// check for child elements, attributes, and text, all of
// which qualify an element as "not null"
//
if (!instances[n].hasChildNodes() &&
!instances[n].hasAttributes() &&
XmlUtils.extractText(instances[n]) == null)
throw new SchemaValidationFault(message);
}
}
}
}
}