001/**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kns.util;
017
018import java.io.ByteArrayOutputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.OutputStream;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.Enumeration;
025import java.util.HashMap;
026import java.util.Hashtable;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031import java.util.regex.Matcher;
032import java.util.regex.Pattern;
033
034import javax.servlet.ServletException;
035import javax.servlet.http.HttpServletRequest;
036import javax.servlet.http.HttpServletResponse;
037import javax.servlet.http.HttpSession;
038import javax.servlet.jsp.PageContext;
039
040import org.apache.commons.lang.StringEscapeUtils;
041import org.apache.commons.lang.StringUtils;
042import org.apache.log4j.Level;
043import org.apache.log4j.Logger;
044import org.apache.struts.Globals;
045import org.apache.struts.action.ActionForm;
046import org.apache.struts.action.ActionMapping;
047import org.apache.struts.action.ActionServletWrapper;
048import org.apache.struts.upload.CommonsMultipartRequestHandler;
049import org.apache.struts.upload.FormFile;
050import org.apache.struts.upload.MultipartRequestHandler;
051import org.apache.struts.upload.MultipartRequestWrapper;
052import org.kuali.rice.core.api.config.property.ConfigurationService;
053import org.kuali.rice.core.api.util.RiceKeyConstants;
054import org.kuali.rice.kew.api.action.ActionRequest;
055import org.kuali.rice.kew.api.action.RecipientType;
056import org.kuali.rice.kim.api.role.Role;
057import org.kuali.rice.kim.api.services.KimApiServiceLocator;
058import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
059import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
060import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
061import org.kuali.rice.kns.service.KNSServiceLocator;
062import org.kuali.rice.kns.web.struts.action.KualiMultipartRequestHandler;
063import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
064import org.kuali.rice.kns.web.struts.form.KualiForm;
065import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
066import org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase;
067import org.kuali.rice.kns.web.ui.Field;
068import org.kuali.rice.kns.web.ui.Row;
069import org.kuali.rice.kns.web.ui.Section;
070import org.kuali.rice.krad.datadictionary.AttributeDefinition;
071import org.kuali.rice.krad.datadictionary.AttributeSecurity;
072import org.kuali.rice.krad.datadictionary.DataDictionary;
073import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
074import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
075import org.kuali.rice.krad.document.Document;
076import org.kuali.rice.krad.exception.ValidationException;
077import org.kuali.rice.krad.service.KRADServiceLocator;
078import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
079import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
080import org.kuali.rice.krad.util.GlobalVariables;
081import org.kuali.rice.krad.util.KRADConstants;
082import org.kuali.rice.krad.util.MessageMap;
083import org.kuali.rice.krad.util.ObjectUtils;
084import org.springframework.transaction.PlatformTransactionManager;
085import org.springframework.transaction.TransactionStatus;
086import org.springframework.transaction.support.TransactionCallback;
087import org.springframework.transaction.support.TransactionTemplate;
088
089/**
090 * General helper methods for handling requests.
091 */
092public class WebUtils {
093        private static final Logger LOG = Logger.getLogger(WebUtils.class);
094
095        private static final String IMAGE_COORDINATE_CLICKED_X_EXTENSION = ".x";
096        private static final String IMAGE_COORDINATE_CLICKED_Y_EXTENSION = ".y";
097
098        private static final String APPLICATION_IMAGE_URL_PROPERTY_PREFIX = "application.custom.image.url";
099        private static final String DEFAULT_IMAGE_URL_PROPERTY_NAME = "kr.externalizable.images.url";
100
101    /**
102     * Prefixes indicating an absolute url
103     */
104    private static final String[] SCHEMES = { "http://", "https://" };
105
106        /**
107         * A request attribute name that indicates that a
108         * {@link org.kuali.rice.kns.exception.FileUploadLimitExceededException} has already been thrown for the
109         * request.
110         */
111        public static final String FILE_UPLOAD_LIMIT_EXCEEDED_EXCEPTION_ALREADY_THROWN = "fileUploadLimitExceededExceptionAlreadyThrown";
112
113        private static ConfigurationService configurationService;
114    private static PlatformTransactionManager transactionManager;
115
116        /**
117         * Checks for methodToCall parameter, and picks off the value using set dot
118         * notation. Handles the problem of image submits.
119         *
120         * @param request
121         * @return methodToCall String
122         */
123        public static String parseMethodToCall(ActionForm form, HttpServletRequest request) {
124                String methodToCall = null;
125
126                // check if is specified cleanly
127                if (StringUtils.isNotBlank(request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER))) {
128                        if (form instanceof KualiForm
129                                        && !((KualiForm) form).shouldMethodToCallParameterBeUsed(KRADConstants.DISPATCH_REQUEST_PARAMETER,
130                                                        request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER), request)) {
131                                throw new RuntimeException("Cannot verify that the methodToCall should be "
132                                                + request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER));
133                        }
134                        methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
135                        // include .x at the end of the parameter to make it consistent w/
136                        // other parameters
137                        request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, KRADConstants.DISPATCH_REQUEST_PARAMETER + "."
138                                        + methodToCall + IMAGE_COORDINATE_CLICKED_X_EXTENSION);
139                }
140
141                /**
142                 * The reason why we are checking for a ".x" at the end of the parameter
143                 * name: It is for the image names that in addition to sending the form
144                 * data, the web browser sends the x,y coordinate of where the user
145                 * clicked on the image. If the image input is not given a name then the
146                 * browser sends the x and y coordinates as the "x" and "y" input
147                 * fields. If the input image does have a name, the x and y coordinates
148                 * are sent using the format name.x and name.y.
149                 */
150                if (methodToCall == null) {
151                        // iterate through parameters looking for methodToCall
152                        for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
153                                String parameterName = (String) i.nextElement();
154
155                                // check if the parameter name is a specifying the methodToCall
156                                if (isMethodToCall(parameterName)) {
157                                        methodToCall = getMethodToCallSettingAttribute(form, request, parameterName);
158                                        break;
159                                }
160                                else {
161                                        // KULRICE-1218: Check if the parameter's values match (not
162                                        // just the name)
163                                        for (String value : request.getParameterValues(parameterName)) {
164                                                // adding period to startsWith check - don't want to get
165                                                // confused with methodToCallFoobar
166                                                if (isMethodToCall(value)) {
167                                                        methodToCall = getMethodToCallSettingAttribute(form, request, value);
168                                                        // why is there not a break outer loop here?
169                                                }
170                                        }
171                                }
172                        }
173                }
174
175                return methodToCall;
176        }
177
178    /**
179         * Checks if a string signifies a methodToCall string
180         *
181         * @param string
182         *            the string to check
183         * @return true if is a methodToCall
184         */
185        private static boolean isMethodToCall(String string) {
186                // adding period to startsWith check - don't want to get confused with
187                // methodToCallFoobar
188                return string.startsWith(KRADConstants.DISPATCH_REQUEST_PARAMETER + ".");
189        }
190
191        /**
192         * Parses out the methodToCall command and also sets the request attribute
193         * for the methodToCall.
194         *
195         * @param form
196         *            the ActionForm
197         * @param request
198         *            the request to set the attribute on
199         * @param string
200         *            the methodToCall string
201         * @return the methodToCall command
202         */
203        private static String getMethodToCallSettingAttribute(ActionForm form, HttpServletRequest request, String string) {
204
205                if (form instanceof KualiForm
206                                && !((KualiForm) form).shouldMethodToCallParameterBeUsed(string, request.getParameter(string), request)) {
207                        throw new RuntimeException("Cannot verify that the methodToCall should be " + string);
208                }
209                // always adding a coordinate even if not an image
210                final String attributeValue = endsWithCoordinates(string) ? string : string
211                                + IMAGE_COORDINATE_CLICKED_X_EXTENSION;
212                final String methodToCall = StringUtils.substringBetween(attributeValue,
213                                KRADConstants.DISPATCH_REQUEST_PARAMETER + ".", ".");
214                request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, attributeValue);
215                return methodToCall;
216        }
217
218        /**
219         * Iterates through and logs (at the given level) all attributes and
220         * parameters of the given request onto the given Logger
221         *
222         * @param request
223         * @param logger
224         */
225        public static void logRequestContents(Logger logger, Level level, HttpServletRequest request) {
226                if (logger.isEnabledFor(level)) {
227                        logger.log(level, "--------------------");
228                        logger.log(level, "HttpRequest attributes:");
229                        for (Enumeration e = request.getAttributeNames(); e.hasMoreElements();) {
230                                String attrName = (String) e.nextElement();
231                                Object attrValue = request.getAttribute(attrName);
232
233                                if (attrValue.getClass().isArray()) {
234                                        logCollection(logger, level, attrName, Arrays.asList((Object[]) attrValue));
235                                }
236                                else if (attrValue instanceof Collection) {
237                                        logCollection(logger, level, attrName, (Collection) attrValue);
238                                }
239                                else if (attrValue instanceof Map) {
240                                        logMap(logger, level, attrName, (Map) attrValue);
241                                }
242                                else {
243                                        logObject(logger, level, attrName, attrValue);
244                                }
245                        }
246
247                        logger.log(level, "--------------------");
248                        logger.log(level, "HttpRequest parameters:");
249                        for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
250                                String paramName = (String) i.nextElement();
251                                String[] paramValues = request.getParameterValues(paramName);
252
253                                logArray(logger, level, paramName, paramValues);
254                        }
255
256                        logger.log(level, "--------------------");
257                }
258        }
259
260        private static void logArray(Logger logger, Level level, String arrayName, Object[] array) {
261                StringBuffer value = new StringBuffer("[");
262                for (int i = 0; i < array.length; ++i) {
263                        if (i > 0) {
264                                value.append(",");
265                        }
266                        value.append(array[i]);
267                }
268                value.append("]");
269
270                logThing(logger, level, arrayName, value);
271        }
272
273        private static void logCollection(Logger logger, Level level, String collectionName, Collection c) {
274                StringBuffer value = new StringBuffer("{");
275                for (Iterator i = c.iterator(); i.hasNext();) {
276                        value.append(i.next());
277                        if (i.hasNext()) {
278                                value.append(",");
279                        }
280                }
281                value.append("}");
282
283                logThing(logger, level, collectionName, value);
284        }
285
286        private static void logMap(Logger logger, Level level, String mapName, Map m) {
287                StringBuffer value = new StringBuffer("{");
288                for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
289                        Map.Entry e = (Map.Entry) i.next();
290                        value.append("('" + e.getKey() + "','" + e.getValue() + "')");
291                }
292                value.append("}");
293
294                logThing(logger, level, mapName, value);
295        }
296
297        private static void logObject(Logger logger, Level level, String objectName, Object o) {
298                logThing(logger, level, objectName, "'" + o + "'");
299        }
300
301        private static void logThing(Logger logger, Level level, String thingName, Object thing) {
302                logger.log(level, "    '" + thingName + "' => " + thing);
303        }
304
305        /**
306         * A file that is not of type text/plain or text/html can be output through
307         * the response using this method.
308         *
309         * @param response
310         * @param contentType
311         * @param byteArrayOutputStream
312         * @param fileName
313         */
314        public static void saveMimeOutputStreamAsFile(HttpServletResponse response, String contentType,
315                        ByteArrayOutputStream byteArrayOutputStream, String fileName) throws IOException {
316
317                // set response
318                response.setContentType(contentType);
319                response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
320                response.setHeader("Expires", "0");
321                response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
322                response.setHeader("Pragma", "public");
323                response.setContentLength(byteArrayOutputStream.size());
324
325                // write to output
326                OutputStream outputStream = response.getOutputStream();
327                byteArrayOutputStream.writeTo(response.getOutputStream());
328                outputStream.flush();
329                outputStream.close();
330        }
331
332        /**
333         * A file that is not of type text/plain or text/html can be output through
334         * the response using this method.
335         *
336         * @param response
337         * @param contentType
338         * @param inStream
339         * @param fileName
340         */
341        public static void saveMimeInputStreamAsFile(HttpServletResponse response, String contentType,
342                        InputStream inStream, String fileName, int fileSize) throws IOException {
343
344                // set response
345                response.setContentType(contentType);
346                response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
347                response.setHeader("Expires", "0");
348                response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
349                response.setHeader("Pragma", "public");
350                response.setContentLength(fileSize);
351
352                // write to output
353                OutputStream out = response.getOutputStream();
354                while (inStream.available() > 0) {
355                        out.write(inStream.read());
356                }
357                out.flush();
358        }
359
360        /**
361         * JSTL function to return the tab state of the tab from the form.
362         *
363         * @param form
364         * @param tabKey
365         * @return
366         */
367        public static String getTabState(KualiForm form, String tabKey) {
368                return form.getTabState(tabKey);
369        }
370
371        public static void incrementTabIndex(KualiForm form, String tabKey) {
372                form.incrementTabIndex();
373        }
374
375    /**
376     * Attempts to reopen sub tabs which would have been closed for inactive records
377     *
378     * @param sections the list of Sections whose rows and fields to set the open tab state on
379     * @param tabStates the map of tabKey->tabState.  This map will be modified to set entries to "OPEN"
380     * @param collectionName the name of the collection reopening
381     */
382    public static void reopenInactiveRecords(List<Section> sections, Map<String, String> tabStates, String collectionName) {
383        for (Section section : sections) {
384            for (Row row: section.getRows()) {
385                for (Field field : row.getFields()) {
386                    if (field != null) {
387                        if (Field.CONTAINER.equals(field.getFieldType()) && StringUtils.startsWith(field.getContainerName(), collectionName)) {
388                            final String tabKey = WebUtils.generateTabKey(FieldUtils.generateCollectionSubTabName(field));
389                            tabStates.put(tabKey, KualiForm.TabState.OPEN.name());
390                        }
391                    }
392                }
393            }
394        }
395    }
396
397        /**
398         * Generates a String from the title that can be used as a Map key.
399         *
400         * @param tabTitle
401         * @return
402         */
403        public static String generateTabKey(String tabTitle) {
404                String key = "";
405                if (!StringUtils.isBlank(tabTitle)) {
406                        key = tabTitle.replaceAll("\\W", "");
407                        // if (key.length() > 25) {
408                        // key = key.substring(0, 24);
409                        // }
410                }
411
412                return key;
413        }
414
415        public static void getMultipartParameters(HttpServletRequest request, ActionServletWrapper servletWrapper,
416                        ActionForm form, ActionMapping mapping) {
417                Map params = new HashMap();
418
419                // Get the ActionServletWrapper from the form bean
420                // ActionServletWrapper servletWrapper = getServletWrapper();
421
422                try {
423                        CommonsMultipartRequestHandler multipartHandler = new CommonsMultipartRequestHandler();
424                        if (multipartHandler != null) {
425                                // Set servlet and mapping info
426                                if (servletWrapper != null) {
427                                        // from pojoformbase
428                                        // servlet only affects tempdir on local disk
429                                        servletWrapper.setServletFor(multipartHandler);
430                                }
431                                multipartHandler.setMapping((ActionMapping) request.getAttribute(Globals.MAPPING_KEY));
432                                // Initialize multipart request class handler
433                                multipartHandler.handleRequest(request);
434
435                                Collection<FormFile> files = multipartHandler.getFileElements().values();
436                                Enumeration keys = multipartHandler.getFileElements().keys();
437
438                                while (keys.hasMoreElements()) {
439                                        Object key = keys.nextElement();
440                                        FormFile file = (FormFile) multipartHandler.getFileElements().get(key);
441                                        long maxSize = WebUtils.getMaxUploadSize(form);
442                                        if (LOG.isDebugEnabled()) {
443                                                LOG.debug(file.getFileSize());
444                                        }
445                                        if (maxSize > 0 && Long.parseLong(file.getFileSize() + "") > maxSize) {
446
447                                                GlobalVariables.getMessageMap().putError(key.toString(),
448                                                                RiceKeyConstants.ERROR_UPLOADFILE_SIZE,
449                                                                new String[] { file.getFileName(), Long.toString(maxSize) });
450
451                                        }
452                                }
453
454                                // get file elements for kualirequestprocessor
455                                if (servletWrapper == null) {
456                                        request.setAttribute(KRADConstants.UPLOADED_FILE_REQUEST_ATTRIBUTE_KEY,
457                                                        getFileParametersForMultipartRequest(request, multipartHandler));
458                                }
459                        }
460                }
461                catch (ServletException e) {
462                        throw new ValidationException("unable to handle multipart request " + e.getMessage(), e);
463                }
464        }
465
466        public static long getMaxUploadSize(ActionForm form) {
467                long max = 0L;
468                KualiMultipartRequestHandler multipartHandler = new KualiMultipartRequestHandler();
469                if (form instanceof PojoFormBase) {
470                        max = multipartHandler.calculateMaxUploadSizeToMaxOfList(((PojoFormBase) form).getMaxUploadSizes());
471                }
472                if (LOG.isDebugEnabled()) {
473                        LOG.debug("Max File Upload Size: " + max);
474                }
475                return max;
476        }
477
478        private static Map getFileParametersForMultipartRequest(HttpServletRequest request,
479                        MultipartRequestHandler multipartHandler) {
480                Map parameters = new HashMap();
481                Hashtable elements = multipartHandler.getFileElements();
482                Enumeration e = elements.keys();
483                while (e.hasMoreElements()) {
484                        String key = (String) e.nextElement();
485                        parameters.put(key, elements.get(key));
486                }
487
488                if (request instanceof MultipartRequestWrapper) {
489                        request = (HttpServletRequest) ((MultipartRequestWrapper) request).getRequest();
490                        e = request.getParameterNames();
491                        while (e.hasMoreElements()) {
492                                String key = (String) e.nextElement();
493                                parameters.put(key, request.getParameterValues(key));
494                        }
495                }
496                else {
497                        LOG.debug("Gathering multipart parameters for unwrapped request");
498                }
499                return parameters;
500        }
501
502        // end multipart
503
504        public static void registerEditableProperty(PojoFormBase form, String editablePropertyName) {
505                form.registerEditableProperty(editablePropertyName);
506        }
507
508        public static boolean isDocumentSession(Document document, PojoFormBase docForm) {
509                boolean sessionDoc = document instanceof org.kuali.rice.krad.document.SessionDocument;
510                boolean dataDictionarySessionDoc = false;
511                if (!sessionDoc) {
512                        DataDictionary dataDictionary = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary();
513                        if (docForm instanceof KualiMaintenanceForm) {
514                                KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) docForm;
515                                if (dataDictionary != null) {
516                                        if (maintenanceForm.getDocTypeName() != null) {
517                        MaintenanceDocumentEntry maintenanceDocumentEntry = (MaintenanceDocumentEntry) dataDictionary.getDocumentEntry(maintenanceForm.getDocTypeName());
518                                                dataDictionarySessionDoc = maintenanceDocumentEntry.isSessionDocument();
519                                        }
520                                }
521                        }
522                        else {
523                                if (document != null && dataDictionary != null) {
524                                        KNSDocumentEntry documentEntry = (KNSDocumentEntry) dataDictionary.getDocumentEntry(document.getClass().getName());
525                                        dataDictionarySessionDoc = documentEntry.isSessionDocument();
526                                }
527                        }
528                }
529                return sessionDoc || dataDictionarySessionDoc;
530        }
531
532        public static boolean isFormSessionDocument(PojoFormBase form) {
533                Document document = null;
534                if (KualiDocumentFormBase.class.isAssignableFrom(form.getClass())) {
535                        KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
536                        document = docForm.getDocument();
537                }
538                return isDocumentSession(document, form);
539        }
540
541        public static String KEY_KUALI_FORM_IN_SESSION = "KualiForm";
542
543        public static ActionForm getKualiForm(PageContext pageContext) {
544                return getKualiForm((HttpServletRequest) pageContext.getRequest());
545        }
546
547        public static ActionForm getKualiForm(HttpServletRequest request) {
548                if (request.getAttribute(KEY_KUALI_FORM_IN_SESSION) != null) {
549                        return (ActionForm) request.getAttribute(KEY_KUALI_FORM_IN_SESSION);
550                }
551                else {
552                        final HttpSession session = request.getSession(false);
553                        return session != null ? (ActionForm) session.getAttribute(KEY_KUALI_FORM_IN_SESSION) : null;
554                }
555        }
556
557        public static boolean isPropertyEditable(Set<String> editableProperties, String propertyName) {
558                if (LOG.isDebugEnabled()) {
559                        LOG.debug("isPropertyEditable(" + propertyName + ")");
560                }
561
562                boolean returnVal = editableProperties == null
563                                || editableProperties.contains(propertyName)
564                                || (getIndexOfCoordinateExtension(propertyName) == -1 ? false : editableProperties
565                                                .contains(propertyName.substring(0, getIndexOfCoordinateExtension(propertyName))));
566                if (!returnVal) {
567                        if (LOG.isDebugEnabled()) {
568                                LOG.debug("isPropertyEditable(" + propertyName + ") == false / editableProperties: "
569                                                + editableProperties);
570                        }
571                }
572                return returnVal;
573        }
574
575        public static boolean endsWithCoordinates(String parameter) {
576                return parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION)
577                                || parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
578        }
579
580        public static int getIndexOfCoordinateExtension(String parameter) {
581                int indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION);
582        if (indexOfCoordinateExtension == -1) {
583            indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
584        }
585                return indexOfCoordinateExtension;
586        }
587
588    public static boolean isInquiryHiddenField(String className, String fieldName, Object formObject, String propertyName) {
589        boolean isHidden = false;
590        String hiddenInquiryFields = getKualiConfigurationService().getPropertyValueAsString(className + ".hidden");
591        if (StringUtils.isEmpty(hiddenInquiryFields)) {
592                return isHidden;
593        }
594        List hiddenFields = Arrays.asList(hiddenInquiryFields.replaceAll(" ", "").split(","));
595        if (hiddenFields.contains(fieldName.trim())) {
596                isHidden = true;
597        }
598        return isHidden;
599    }
600
601    public static boolean isHiddenKimObjectType(String type, String configParameter) {
602        boolean hideType = false;
603        String hiddenTypes = getKualiConfigurationService().getPropertyValueAsString(configParameter);
604        if (StringUtils.isEmpty(hiddenTypes)) {
605                return hideType;
606        }
607        List hiddenTypeValues = Arrays.asList(hiddenTypes.replaceAll(" ", "").split(","));
608        if (hiddenTypeValues.contains(type.trim())) {
609                hideType = true;
610        }
611        return hideType;
612    }
613
614        public static String getFullyMaskedValue(String className, String fieldName, Object formObject, String propertyName) {
615                String displayMaskValue = null;
616                Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
617
618                DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
619                                .getDataDictionary().getDictionaryObjectEntry(className);
620                AttributeDefinition a = entry.getAttributeDefinition(fieldName);
621
622                AttributeSecurity attributeSecurity = a.getAttributeSecurity();
623                if (attributeSecurity != null && attributeSecurity.isMask()) {
624                        MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter();
625                        displayMaskValue = maskFormatter.maskValue(propertyValue);
626
627                }
628                return displayMaskValue;
629        }
630
631        public static String getPartiallyMaskedValue(String className, String fieldName, Object formObject,
632                        String propertyName) {
633                String displayMaskValue = null;
634                Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
635
636                DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
637                                .getDataDictionary().getDictionaryObjectEntry(className);
638                AttributeDefinition a = entry.getAttributeDefinition(fieldName);
639
640                AttributeSecurity attributeSecurity = a.getAttributeSecurity();
641                if (attributeSecurity != null && attributeSecurity.isPartialMask()) {
642                        MaskFormatter partialMaskFormatter = attributeSecurity.getPartialMaskFormatter();
643                        displayMaskValue = partialMaskFormatter.maskValue(propertyValue);
644
645                }
646                return displayMaskValue;
647        }
648
649        public static boolean canFullyUnmaskField(final String businessObjectClassName, final String fieldName, final KualiForm form) {
650                try {
651                    final Class businessObjClass = Class.forName(businessObjectClassName);
652                TransactionTemplate template = new TransactionTemplate(getTransactionManager());
653                return template.execute(new TransactionCallback<Boolean>() {
654                    @Override
655                    public Boolean doInTransaction(TransactionStatus status) {
656                        if (form instanceof KualiDocumentFormBase) {
657                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
658                                    GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
659                                    ((KualiDocumentFormBase) form).getDocument());
660                        }
661                        else {
662                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
663                                    GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
664                        }
665                    }
666                });
667                }
668                catch (Exception e) {
669                        throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
670                }
671        }
672
673        public static boolean canPartiallyUnmaskField(String businessObjectClassName, final String fieldName, final KualiForm form) {
674                try {
675                        final Class businessObjClass = Class.forName(businessObjectClassName);
676                TransactionTemplate template = new TransactionTemplate(getTransactionManager());
677                return template.execute(new TransactionCallback<Boolean>() {
678                    @Override
679                    public Boolean doInTransaction(TransactionStatus status) {
680                        if (form instanceof KualiDocumentFormBase) {
681                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
682                                    GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
683                                    ((KualiDocumentFormBase) form).getDocument());
684                        }
685                        else {
686                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
687                                    GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
688                        }
689                    }
690                });
691                }
692                catch (Exception e) {
693                        throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
694                }
695        }
696
697        public static boolean canAddNoteAttachment(final Document document) {
698        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
699        return template.execute(new TransactionCallback<Boolean>() {
700            @Override
701            public Boolean doInTransaction(TransactionStatus status) {
702                boolean canViewNoteAttachment = false;
703                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
704                        document);
705                canViewNoteAttachment = documentAuthorizer.canAddNoteAttachment(document, null, GlobalVariables
706                        .getUserSession().getPerson());
707                return canViewNoteAttachment;
708            }
709        });
710        }
711
712        public static boolean canViewNoteAttachment(final Document document, final String attachmentTypeCode) {
713        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
714        return template.execute(new TransactionCallback<Boolean>() {
715            @Override
716            public Boolean doInTransaction(TransactionStatus status) {
717                boolean canViewNoteAttachment = false;
718                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
719                        document);
720                canViewNoteAttachment = documentAuthorizer.canViewNoteAttachment(document, attachmentTypeCode, GlobalVariables
721                        .getUserSession().getPerson());
722                return canViewNoteAttachment;
723            }
724        });
725        }
726
727        public static boolean canDeleteNoteAttachment(final Document document, final String attachmentTypeCode, final String authorUniversalIdentifier) {
728        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
729        return template.execute(new TransactionCallback<Boolean>() {
730            @Override
731            public Boolean doInTransaction(TransactionStatus status) {
732                boolean canDeleteNoteAttachment = false;
733                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
734                        document);
735                canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "false",
736                        GlobalVariables.getUserSession().getPerson());
737                if (canDeleteNoteAttachment) {
738                    return canDeleteNoteAttachment;
739                }
740                else {
741                    canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "true",
742                            GlobalVariables.getUserSession().getPerson());
743                    if (canDeleteNoteAttachment
744                            && !authorUniversalIdentifier.equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())) {
745                        canDeleteNoteAttachment = false;
746                    }
747                }
748                return canDeleteNoteAttachment;
749            }
750        });
751        }
752
753        public static void reuseErrorMapFromPreviousRequest(KualiDocumentFormBase kualiDocumentFormBase) {
754                if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == null) {
755                        LOG.error("Error map from previous request is null!");
756                        return;
757                }
758                MessageMap errorMapFromGlobalVariables = GlobalVariables.getMessageMap();
759                if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == errorMapFromGlobalVariables) {
760                        // if we've switched them already, then return early and do nothing
761                        return;
762                }
763                if (!errorMapFromGlobalVariables.hasNoErrors()) {
764                        throw new RuntimeException("Cannot replace error map because it is not empty");
765                }
766                GlobalVariables.setMessageMap(kualiDocumentFormBase.getMessageMapFromPreviousRequest());
767                GlobalVariables.getMessageMap().clearErrorPath();
768        }
769
770        /**
771         * Excapes out HTML to prevent XSS attacks, and replaces the following
772         * strings to allow for a limited set of HTML tags
773         *
774         * <li>[X] and [/X], where X represents any 1 or 2 letter string may be used
775         * to specify the equivalent tag in HTML (i.e. &lt;X&gt; and &lt;/X&gt;) <li>
776         * [font COLOR], where COLOR represents any valid html color (i.e. color
777         * name or hexcode preceeded by #) will be filtered into &lt;font
778         * color="COLOR"/&gt; <li>[/font] will be filtered into &lt;/font&gt; <li>
779         * [table CLASS], where CLASS gives the style class to use, will be filter
780         * into &lt;table class="CLASS"/&gt; <li>[/table] will be filtered into
781         * &lt;/table&gt; <li>[td CLASS], where CLASS gives the style class to use,
782         * will be filter into &lt;td class="CLASS"/&gt;
783         *
784         * @param inputString
785         * @return
786         */
787        public static String filterHtmlAndReplaceRiceMarkup(String inputString) {
788                String outputString = StringEscapeUtils.escapeHtml(inputString);
789                // string has been escaped of all <, >, and & (and other characters)
790
791                Map<String, String> findAndReplacePatterns = new HashMap<String, String>();
792
793                // now replace our rice custom markup into html
794
795                // DON'T ALLOW THE SCRIPT TAG OR ARBITRARY IMAGES/URLS/ETC. THROUGH
796
797                // filter any one character tags
798                findAndReplacePatterns.put("\\[([A-Za-z])\\]", "<$1>");
799                findAndReplacePatterns.put("\\[/([A-Za-z])\\]", "</$1>");
800                // filter any two character tags
801                findAndReplacePatterns.put("\\[([A-Za-z]{2})\\]", "<$1>");
802                findAndReplacePatterns.put("\\[/([A-Za-z]{2})\\]", "</$1>");
803                // filter the font tag
804                findAndReplacePatterns.put("\\[font (#[0-9A-Fa-f]{1,6}|[A-Za-z]+)\\]", "<font color=\"$1\">");
805                findAndReplacePatterns.put("\\[/font\\]", "</font>");
806                // filter the table tag
807                findAndReplacePatterns.put("\\[table\\]", "<table>");
808                findAndReplacePatterns.put("\\[table ([A-Za-z]+)\\]", "<table class=\"$1\">");
809                findAndReplacePatterns.put("\\[/table\\]", "</table>");
810                // fiter td with class
811                findAndReplacePatterns.put("\\[td ([A-Za-z]+)\\]", "<td class=\"$1\">");
812
813                for (String findPattern : findAndReplacePatterns.keySet()) {
814                        Pattern p = Pattern.compile(findPattern);
815                        Matcher m = p.matcher(outputString);
816                        if (m.find()) {
817                                String replacePattern = findAndReplacePatterns.get(findPattern);
818                                outputString = m.replaceAll(replacePattern);
819                        }
820                }
821
822                return outputString;
823        }
824
825    /**
826         * Determines and returns the URL for question button images; looks first
827         * for a property "application.custom.image.url", and if that is missing,
828         * uses the image url returned by getDefaultButtonImageUrl()
829         *
830         * @param imageName
831         *            the name of the image to find a button for
832         * @return the URL where question button images are located
833         */
834        public static String getButtonImageUrl(String imageName) {
835                String buttonImageUrl = getKualiConfigurationService().getPropertyValueAsString(
836                WebUtils.APPLICATION_IMAGE_URL_PROPERTY_PREFIX + "." + imageName);
837                if (StringUtils.isBlank(buttonImageUrl)) {
838                        buttonImageUrl = getDefaultButtonImageUrl(imageName);
839                }
840                return buttonImageUrl;
841        }
842
843    public static String getAttachmentImageForUrl(String contentType) {
844        String image = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_PREFIX + contentType);
845        if (StringUtils.isEmpty(image)) {
846            return getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_DEFAULT);
847        }
848        return image;
849    }
850
851        /**
852         * Generates a default button image URL, in the form of:
853         * ${kr.externalizable.images.url}buttonsmall_${imageName}.gif
854         *
855         * @param imageName
856         *            the image name to generate a default button name for
857         * @return the default button image url
858         */
859        public static String getDefaultButtonImageUrl(String imageName) {
860                return getKualiConfigurationService().getPropertyValueAsString(WebUtils.DEFAULT_IMAGE_URL_PROPERTY_NAME)
861                                + "buttonsmall_" + imageName + ".gif";
862        }
863
864    /**
865         * @return an implementation of the KualiConfigurationService
866         */
867        public static ConfigurationService getKualiConfigurationService() {
868                if (configurationService == null) {
869                        configurationService = KRADServiceLocator.getKualiConfigurationService();
870                }
871                return configurationService;
872        }
873    public static PlatformTransactionManager getTransactionManager() {
874        if ( transactionManager == null ) {
875            transactionManager = KRADServiceLocatorInternal.getTransactionManager();
876        }
877        return transactionManager;
878    }
879
880    /**
881     * Takes a string an converts the whitespace which would be ignored in an
882     * HTML document into HTML elements so the whitespace is preserved
883     *
884     * @param startingString The string to preserve whitespace in
885     * @return A string whose whitespace has been converted to HTML elements to preserve the whitespace in an HTML document
886     */
887    public static String preserveWhitespace(String startingString) {
888        String convertedString = startingString.replaceAll("\n", "<br />");
889        convertedString = convertedString.replaceAll("  ", "&nbsp;&nbsp;").replaceAll("(&nbsp; | &nbsp;)", "&nbsp;&nbsp;");
890        return convertedString;
891    }
892
893    public static String getKimGroupDisplayName(final String groupId) {
894        if(StringUtils.isBlank(groupId)) {
895                throw new IllegalArgumentException("Group ID must have a value");
896        }
897        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
898        return template.execute(new TransactionCallback<String>() {
899            @Override
900            public String doInTransaction(TransactionStatus status) {
901                return KimApiServiceLocator.getGroupService().getGroup(groupId).getName();
902            }
903        });
904    }
905
906    public static String getPrincipalDisplayName(final String principalId) {
907        if(StringUtils.isBlank(principalId)) {
908                throw new IllegalArgumentException("Principal ID must have a value");
909        }
910        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
911        return template.execute(new TransactionCallback<String>() {
912            @Override
913            public String doInTransaction(TransactionStatus status) {
914                return KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId).getDefaultName().getCompositeName();
915            }
916        });
917    }
918
919    /**
920     * Takes an {@link org.kuali.rice.kew.api.action.ActionRequest} with a recipient type of
921     * {@link org.kuali.rice.kew.api.action.RecipientType#ROLE} and returns the display name for the role.
922     *
923     * @param actionRequest the action request
924     * @return the display name for the role
925     * @throws IllegalArgumentException if the action request is null, or the recipient type is not ROLE
926     */
927    public static String getRoleDisplayName(final ActionRequest actionRequest) {
928        if(actionRequest == null) {
929            throw new IllegalArgumentException("actionRequest must be non-null");
930        }
931        if (RecipientType.ROLE != actionRequest.getRecipientType()) {
932            throw new IllegalArgumentException("actionRequest recipient must be a Role");
933        }
934        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
935        return template.execute(new TransactionCallback<String>() {
936            @Override
937            public String doInTransaction(TransactionStatus status) {
938                String result = "";
939                Role role = KimApiServiceLocator.getRoleService().getRole(actionRequest.getRoleName());
940
941                if (role != null) {
942                    result = role.getName();
943                } else if (!StringUtils.isBlank(actionRequest.getQualifiedRoleNameLabel())) {
944                    result = actionRequest.getQualifiedRoleNameLabel();
945                } else {
946                    result = actionRequest.getRoleName();
947                }
948
949                return result;
950            }
951        });
952    }
953
954    /**
955     * Returns an absolute URL which is a combination of a base part plus path,
956     * or in the case that the path is already an absolute URL, the path alone
957     * @param base the url base path
958     * @param path the path to append to base
959     * @return an absolute URL representing the combination of base+path, or path alone if it is already absolute
960     */
961    public static String toAbsoluteURL(String base, String path) {
962        boolean abs = false;
963        if (StringUtils.isBlank(path)) {
964            path = "";
965        } else {
966            for (String scheme: SCHEMES) {
967                if (path.startsWith(scheme)) {
968                    abs = true;
969                    break;
970                }
971            }
972        }
973        if (abs) {
974            return path;
975        }
976        return base + path;
977    }
978
979}