001/** 002 * 003 * Copyright 2020-2021 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.formtypes; 018 019import java.util.HashMap; 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022import java.util.logging.Logger; 023 024import org.jivesoftware.smack.util.Objects; 025import org.jivesoftware.smack.util.StringUtils; 026import org.jivesoftware.smack.util.XmlUtil; 027 028import org.jivesoftware.smackx.xdata.FormField; 029import org.jivesoftware.smackx.xdata.TextSingleFormField; 030import org.jivesoftware.smackx.xdata.packet.DataForm; 031 032public class FormFieldRegistry { 033 034 private static final Logger LOGGER = Logger.getLogger(FormFieldRegistry.class.getName()); 035 036 private static final Map<String, Map<String, FormField.Type>> REGISTRY = new HashMap<>(); 037 038 private static final Map<String, FormField.Type> CLARK_NOTATION_FIELD_REGISTRY = new ConcurrentHashMap<>(); 039 040 private static final Map<String, FormField.Type> LOOKASIDE_FIELD_REGISTRY = new ConcurrentHashMap<>(); 041 042 @SuppressWarnings("ReferenceEquality") 043 public static void register(DataForm dataForm) { 044 // TODO: Also allow forms of type 'result'? 045 if (dataForm.getType() != DataForm.Type.form) { 046 throw new IllegalArgumentException(); 047 } 048 049 String formType = null; 050 TextSingleFormField hiddenFormTypeField = dataForm.getHiddenFormTypeField(); 051 if (hiddenFormTypeField != null) { 052 formType = hiddenFormTypeField.getValue(); 053 } 054 055 for (FormField formField : dataForm.getFields()) { 056 // Note that we can compare here by reference equality to skip the hidden form type field. 057 if (formField == hiddenFormTypeField) { 058 continue; 059 } 060 061 FormField.Type type = formField.getType(); 062 if (type == FormField.Type.fixed) { 063 continue; 064 } 065 066 String fieldName = formField.getFieldName(); 067 register(formType, fieldName, type); 068 } 069 } 070 071 public static void register(String formType, FormField.Type fieldType, String... fieldNames) { 072 for (String fieldName : fieldNames) { 073 register(formType, fieldName, fieldType); 074 } 075 } 076 077 public static void register(String formType, String fieldName, FormField.Type fieldType) { 078 StringUtils.requireNotNullNorEmpty(fieldName, "fieldName must be provided"); 079 Objects.requireNonNull(fieldType); 080 081 if (formType == null) { 082 if (XmlUtil.isClarkNotation(fieldName)) { 083 CLARK_NOTATION_FIELD_REGISTRY.put(fieldName, fieldType); 084 } 085 return; 086 } 087 088 FormField.Type previousType; 089 synchronized (REGISTRY) { 090 Map<String, FormField.Type> fieldNameToType = REGISTRY.get(formType); 091 if (fieldNameToType == null) { 092 fieldNameToType = new HashMap<>(); 093 REGISTRY.put(formType, fieldNameToType); 094 } else { 095 previousType = fieldNameToType.get(fieldName); 096 if (previousType != null && previousType != fieldType) { 097 String message = "The field '" + fieldName + "' from form type '" + formType 098 + "' was already registered with field type '" + previousType 099 + "' while it is now seen with type '" + fieldType 100 + "'. Form field types have to be unambigiously." 101 + " XMPP uses a registry for form field tpes, scoped by the form type." 102 + " You may find the correct value at https://xmpp.org/registrar/formtypes.html"; 103 throw new IllegalArgumentException(message); 104 } 105 } 106 previousType = fieldNameToType.put(fieldName, fieldType); 107 } 108 if (previousType != null && fieldType != previousType) { 109 LOGGER.warning("Form field registry inconsitency detected: Registered field '" + fieldName + "' of type " + fieldType + " but previous type was " + previousType); 110 } 111 112 } 113 114 public static FormField.Type lookup(String formType, String fieldName) { 115 if (formType == null) { 116 if (XmlUtil.isClarkNotation(fieldName)) { 117 return CLARK_NOTATION_FIELD_REGISTRY.get(fieldName); 118 } 119 120 return LOOKASIDE_FIELD_REGISTRY.get(fieldName); 121 } 122 123 synchronized (REGISTRY) { 124 Map<String, FormField.Type> fieldNameToTypeMap = REGISTRY.get(formType); 125 if (fieldNameToTypeMap != null) { 126 FormField.Type type = fieldNameToTypeMap.get(fieldName); 127 if (type != null) { 128 return type; 129 } 130 } 131 } 132 133 return null; 134 } 135 136 public static synchronized FormField.Type lookup(String fieldName) { 137 return lookup(null, fieldName); 138 } 139 140 public static void addLookasideFieldRegistryEntry(String fieldName, FormField.Type formFieldType) { 141 LOOKASIDE_FIELD_REGISTRY.put(fieldName, formFieldType); 142 } 143}