001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.acacia.client;
029
030import org.opencms.acacia.shared.CmsContentDefinition;
031import org.opencms.acacia.shared.CmsEntity;
032import org.opencms.acacia.shared.CmsValidationResult;
033import org.opencms.acacia.shared.rpc.I_CmsContentServiceAsync;
034import org.opencms.gwt.client.ui.CmsTabbedPanel;
035import org.opencms.util.CmsPair;
036
037import java.util.Collection;
038import java.util.HashSet;
039import java.util.Map.Entry;
040import java.util.Set;
041
042import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
043import com.google.gwt.event.logical.shared.ValueChangeEvent;
044import com.google.gwt.event.logical.shared.ValueChangeHandler;
045import com.google.gwt.event.shared.EventHandler;
046import com.google.gwt.event.shared.GwtEvent;
047import com.google.gwt.event.shared.HandlerRegistration;
048import com.google.gwt.event.shared.SimpleEventBus;
049import com.google.gwt.user.client.Timer;
050import com.google.gwt.user.client.rpc.AsyncCallback;
051
052/**
053 * Validation handler.<p>
054 */
055public final class CmsValidationHandler
056implements ValueChangeHandler<CmsEntity>, HasValueChangeHandlers<CmsValidationContext> {
057
058    /**
059     * The validation timer.<p>
060     */
061    protected class ValidationTimer extends Timer {
062
063        /** The entity to validate. */
064        private CmsEntity m_entity;
065
066        /**
067         * Constructor.<p>
068         *
069         * @param entity the entity to validate
070         */
071        protected ValidationTimer(CmsEntity entity) {
072
073            m_entity = entity;
074        }
075
076        /**
077         * @see com.google.gwt.user.client.Timer#run()
078         */
079        @Override
080        public void run() {
081
082            validate(m_entity);
083            m_validationTimer = null;
084        }
085    }
086
087    /** Flag indicating the a validation call is running. */
088    boolean m_validating;
089
090    /** The current validation timer instance. */
091    Timer m_validationTimer;
092
093    /** The content service use for validation. */
094    private I_CmsContentServiceAsync m_contentService;
095
096    /** The event bus. */
097    private SimpleEventBus m_eventBus;
098
099    /** The forms tabbed panel. */
100    private CmsTabbedPanel<?> m_formTabPanel;
101
102    /** The handler registration. */
103    private HandlerRegistration m_handlerRegistration;
104
105    /** Indicates validation is paused. */
106    private boolean m_paused;
107
108    /** The root attribute handler. */
109    private CmsRootHandler m_rootHandler;
110
111    /** The validation context. */
112    private CmsValidationContext m_validationContext;
113
114    /** Paths (without indexes) for which values are synchronized over the different locales. E.g. Availability/ReleaseDate */
115    private Set<String> m_synchronizedPaths;
116
117    /**
118     * Clears validation message for an attribute handler.<p>
119     *
120     * @param handler the handler for which to clear the validation message
121     */
122    public static void clearValidation(I_CmsAttributeHandler handler) {
123
124        if (handler instanceof CmsAttributeHandler) {
125            ((CmsAttributeHandler)handler).removeValidationMessages();
126        }
127
128    }
129
130    /**
131     * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
132     */
133    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<CmsValidationContext> handler) {
134
135        return addHandler(handler, ValueChangeEvent.getType());
136    }
137
138    /**
139     * Destroys the current handler instance.<p>
140     */
141    public void clear() {
142
143        if (m_handlerRegistration != null) {
144            m_handlerRegistration.removeHandler();
145            m_handlerRegistration = null;
146        }
147        m_validationContext = null;
148        m_paused = false;
149        m_validating = false;
150        if (m_validationTimer != null) {
151            m_validationTimer.cancel();
152            m_validationTimer = null;
153        }
154    }
155
156    /**
157     * Displays the given error messages within the form.<p>
158     *
159     * @param entityId the entity id
160     * @param validationResult the validationResult
161     */
162    public void displayValidation(String entityId, CmsValidationResult validationResult) {
163
164        if (m_formTabPanel != null) {
165            CmsAttributeHandler.clearErrorStyles(m_formTabPanel);
166        }
167        m_rootHandler.visit(CmsValidationHandler::clearValidation);
168        if (validationResult.hasWarnings(entityId)) {
169            for (Entry<String[], CmsPair<String, String>> warning : validationResult.getWarnings(entityId).entrySet()) {
170                String[] pathElements = warning.getKey();
171                // check if there are no errors for this attribute
172                if (!validationResult.hasErrors(entityId)
173                    || !validationResult.getErrors(entityId).containsKey(pathElements)) {
174                    CmsAttributeHandler handler = m_rootHandler.getHandlerByPath(pathElements);
175                    if (handler != null) {
176                        String attributeName = pathElements[pathElements.length - 1];
177                        handler.setWarningMessage(
178                            CmsContentDefinition.extractIndex(attributeName),
179                            warning.getValue().getFirst(),
180                            m_formTabPanel);
181                    }
182                }
183            }
184            m_validationContext.setWarningEntity(entityId, validationResult.getWarnings(entityId));
185        } else {
186            m_validationContext.clearWarningEntity(entityId);
187        }
188        if (validationResult.hasErrors(entityId)) {
189            for (Entry<String[], CmsPair<String, String>> error : validationResult.getErrors(entityId).entrySet()) {
190                String[] pathElements = error.getKey();
191                CmsAttributeHandler handler = m_rootHandler.getHandlerByPath(pathElements);
192                if (handler != null) {
193                    String attributeName = pathElements[pathElements.length - 1];
194                    handler.setErrorMessage(
195                        CmsContentDefinition.extractIndex(attributeName),
196                        error.getValue().getFirst(),
197                        m_formTabPanel);
198                }
199            }
200            m_validationContext.setInvalidEntity(entityId, validationResult.getErrors(entityId));
201        } else {
202            m_validationContext.setValidEntity(entityId);
203        }
204        ValueChangeEvent.fire(this, m_validationContext);
205        m_validating = false;
206    }
207
208    /**
209     * @see com.google.gwt.event.shared.HasHandlers#fireEvent(com.google.gwt.event.shared.GwtEvent)
210     */
211    public void fireEvent(GwtEvent<?> event) {
212
213        ensureHandlers().fireEventFromSource(event, this);
214    }
215
216    /**
217     * Returns the validation context.<p>
218     *
219     * @return the validation context
220     */
221    public CmsValidationContext getValidationContext() {
222
223        return m_validationContext;
224    }
225
226    /**
227     * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
228     */
229    public void onValueChange(final ValueChangeEvent<CmsEntity> event) {
230
231        if (!m_paused) {
232            if (m_validationTimer != null) {
233                m_validationTimer.cancel();
234            }
235            m_validationTimer = new ValidationTimer(event.getValue());
236            m_validationTimer.schedule(300);
237        }
238    }
239
240    /**
241     * Registers the validation handler for the given entity.<p>
242     *
243     * @param entity the entity
244     */
245    public void registerEntity(CmsEntity entity) {
246
247        if (m_validationContext == null) {
248            m_validationContext = new CmsValidationContext();
249        }
250        if (m_handlerRegistration != null) {
251            m_handlerRegistration.removeHandler();
252        }
253        m_paused = false;
254        m_handlerRegistration = entity.addValueChangeHandler(this);
255    }
256
257    /**
258     * Sets the content service used for validation.<p>
259     *
260     * @param contentService the content service
261     */
262    public void setContentService(I_CmsContentServiceAsync contentService) {
263
264        m_contentService = contentService;
265    }
266
267    /**
268     * Sets the form tabbed panel.<p>
269     *
270     * @param tabPanel the tabbed panel
271     */
272    public void setFormTabPanel(CmsTabbedPanel<?> tabPanel) {
273
274        m_formTabPanel = tabPanel;
275    }
276
277    /**
278     * Sets the validation to pause.<p>
279     *
280     * @param paused <code>true</code> to pause the validation
281     * @param entity the entity will be revalidated when setting paused to <code>false</code>
282     */
283    public void setPaused(boolean paused, CmsEntity entity) {
284
285        if (paused != m_paused) {
286            m_paused = paused;
287            if (m_paused) {
288                if (m_validationTimer != null) {
289                    m_validationTimer.cancel();
290                    m_validationTimer = null;
291                }
292            } else {
293                m_validationTimer = new ValidationTimer(entity);
294                m_validationTimer.schedule(300);
295            }
296
297        }
298    }
299
300    /**
301     * Sets the root attribute handler.<p>
302     *
303     * @param rootHandler the root attribute handler
304     */
305    public void setRootHandler(CmsRootHandler rootHandler) {
306
307        m_rootHandler = rootHandler;
308    }
309
310    public void setSynchronizedValues(Collection<String> synchronizedValues) {
311
312        if (null != synchronizedValues) {
313            m_synchronizedPaths = new HashSet<>(synchronizedValues.size());
314            for (String sval : synchronizedValues) {
315                String path = "";
316                boolean isNested = true;
317                while (isNested) {
318                    int slashIdx = sval.indexOf('/');
319                    isNested = slashIdx == 0;
320                    if (isNested) {
321                        sval = sval.substring(1);
322                        slashIdx = sval.indexOf('/');
323                        int colonIdx = sval.indexOf(':');
324                        int idx = colonIdx < slashIdx ? colonIdx : slashIdx;
325                        path += sval.substring(0, idx) + "/";
326                        sval = sval.substring(idx);
327                    } else {
328                        path += CmsContentDefinition.removeIndex(sval.substring(sval.lastIndexOf('/') + 1));
329                    }
330                }
331                m_synchronizedPaths.add(path);
332            }
333        }
334    }
335
336    /**
337     * Update the validation context, i.e., replace it with the one generated from the validation result.
338     * @param validationResult the result to update the context for.
339     */
340    public void updateValidationContext(final CmsValidationResult validationResult) {
341
342        m_validationContext = new CmsValidationContext(validationResult, m_synchronizedPaths);
343    }
344
345    /**
346     * Adds this handler to the widget.
347     *
348     * @param <H> the type of handler to add
349     * @param type the event type
350     * @param handler the handler
351     * @return {@link HandlerRegistration} used to remove the handler
352     */
353    protected <H extends EventHandler> HandlerRegistration addHandler(final H handler, GwtEvent.Type<H> type) {
354
355        return ensureHandlers().addHandlerToSource(type, this, handler);
356    }
357
358    /**
359     * Validates the given entity.<p>
360     *
361     * @param entity the entity
362     */
363    protected void validate(final CmsEntity entity) {
364
365        if (!m_validating) {
366            m_validating = true;
367            m_contentService.validateEntity(entity, new AsyncCallback<CmsValidationResult>() {
368
369                public void onFailure(Throwable caught) {
370
371                    // can be ignored
372                }
373
374                public void onSuccess(CmsValidationResult result) {
375
376                    displayValidation(entity.getId(), result);
377                }
378            });
379        }
380    }
381
382    /**
383     * Lazy initializing the handler manager.<p>
384     *
385     * @return the handler manager
386     */
387    private SimpleEventBus ensureHandlers() {
388
389        if (m_eventBus == null) {
390            m_eventBus = new SimpleEventBus();
391        }
392        return m_eventBus;
393    }
394}