001// Copyright 2007, 2008, 2010 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.services;
016
017import org.apache.tapestry5.*;
018import org.apache.tapestry5.commons.services.PropertyAccess;
019import org.apache.tapestry5.commons.services.TypeCoercer;
020import org.apache.tapestry5.commons.util.ExceptionUtils;
021import org.apache.tapestry5.internal.util.Holder;
022import org.apache.tapestry5.ioc.internal.util.InternalUtils;
023
024@SuppressWarnings("all")
025public class FieldValidationSupportImpl implements FieldValidationSupport
026{
027    private final TypeCoercer typeCoercer;
028
029    private final PropertyAccess propertyAccess;
030
031    public FieldValidationSupportImpl(TypeCoercer typeCoercer, PropertyAccess propertyAccess)
032    {
033        this.typeCoercer = typeCoercer;
034        this.propertyAccess = propertyAccess;
035    }
036
037    public String toClient(Object value, ComponentResources componentResources, FieldTranslator<Object> translator,
038                           NullFieldStrategy nullFieldStrategy)
039    {
040        assert componentResources != null;
041        assert translator != null;
042        assert nullFieldStrategy != null;
043        final Holder<String> resultHolder = Holder.create();
044
045        ComponentEventCallback callback = new ComponentEventCallback()
046        {
047            public boolean handleResult(Object result)
048            {
049                // What's nice is that the ComponentEventException will automatically identify
050                // the method description.
051
052                if (!(result instanceof String))
053                    throw new RuntimeException("Return value from 'parseClient' event handler method must be a string.");
054
055                resultHolder.put((String) result);
056
057                return true;
058            }
059        };
060
061        componentResources.triggerEvent(EventConstants.TO_CLIENT, new Object[]
062                {value}, callback);
063
064        if (resultHolder.hasValue())
065            return resultHolder.get();
066
067        Object effectiveValue = value;
068
069        if (effectiveValue == null)
070        {
071            effectiveValue = nullFieldStrategy.replaceToClient();
072
073            // Don't try to coerce or translate null.
074
075            if (effectiveValue == null)
076                return null;
077        }
078
079        // And now, whether its a value from a bound property, or from the null field strategy,
080        // get it into the right format for the translator and let it translate.
081
082        Object coerced = typeCoercer.coerce(effectiveValue, translator.getType());
083
084        return translator.toClient(coerced);
085    }
086
087    public Object parseClient(String clientValue, ComponentResources componentResources,
088                              FieldTranslator<Object> translator, NullFieldStrategy nullFieldStrategy) throws ValidationException
089    {
090        assert componentResources != null;
091        assert translator != null;
092        assert nullFieldStrategy != null;
093        String effectiveValue = clientValue;
094
095        if (InternalUtils.isBlank(effectiveValue))
096        {
097            effectiveValue = nullFieldStrategy.replaceFromClient();
098
099            if (effectiveValue == null)
100                return null;
101        }
102
103        final Holder<Object> resultHolder = Holder.create();
104
105        ComponentEventCallback callback = new ComponentEventCallback()
106        {
107            public boolean handleResult(Object result)
108            {
109                resultHolder.put(result);
110                return true;
111            }
112        };
113
114        try
115        {
116            componentResources.triggerEvent(EventConstants.PARSE_CLIENT, new Object[]
117                    {effectiveValue}, callback);
118        } catch (RuntimeException ex)
119        {
120            rethrowValidationException(ex);
121        }
122
123        if (resultHolder.hasValue())
124            return resultHolder.get();
125
126        return translator.parse(effectiveValue);
127    }
128
129    /**
130     * Checks for a {@link org.apache.tapestry5.ValidationException} inside the outer exception and throws that,
131     * otherwise rethrows the runtime exception.
132     *
133     * @param outerException initially caught exception
134     * @throws ValidationException if found
135     */
136    private void rethrowValidationException(RuntimeException outerException) throws ValidationException
137    {
138        ValidationException ve = ExceptionUtils.findCause(outerException, ValidationException.class, propertyAccess);
139
140        if (ve != null)
141            throw ve;
142
143        throw outerException;
144    }
145
146    @SuppressWarnings(
147            {"unchecked"})
148    public void validate(Object value, ComponentResources componentResources, FieldValidator validator)
149            throws ValidationException
150    {
151        assert componentResources != null;
152        assert validator != null;
153        validator.validate(value);
154
155        try
156        {
157            componentResources.triggerEvent(EventConstants.VALIDATE, new Object[]
158                    {value}, null);
159        } catch (RuntimeException ex)
160        {
161            rethrowValidationException(ex);
162        }
163    }
164}