001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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.apache.camel.reifier.rest; 018 019import java.util.HashMap; 020import java.util.Map; 021import javax.xml.bind.JAXBContext; 022 023import org.apache.camel.CamelContext; 024import org.apache.camel.ExtendedCamelContext; 025import org.apache.camel.model.rest.RestBindingDefinition; 026import org.apache.camel.processor.RestBindingAdvice; 027import org.apache.camel.spi.DataFormat; 028import org.apache.camel.spi.RestConfiguration; 029import org.apache.camel.spi.RouteContext; 030import org.apache.camel.support.PropertyBindingSupport; 031 032public class RestBindingReifier { 033 034 private final RestBindingDefinition definition; 035 036 public RestBindingReifier(RestBindingDefinition definition) { 037 this.definition = definition; 038 } 039 040 public RestBindingAdvice createRestBindingAdvice(RouteContext routeContext) throws Exception { 041 042 CamelContext context = routeContext.getCamelContext(); 043 RestConfiguration config = context.getRestConfiguration(definition.getComponent(), true); 044 045 // these options can be overridden per rest verb 046 String mode = config.getBindingMode().name(); 047 if (definition.getBindingMode() != null) { 048 mode = definition.getBindingMode().name(); 049 } 050 boolean cors = config.isEnableCORS(); 051 if (definition.getEnableCORS() != null) { 052 cors = definition.getEnableCORS(); 053 } 054 boolean skip = config.isSkipBindingOnErrorCode(); 055 if (definition.getSkipBindingOnErrorCode() != null) { 056 skip = definition.getSkipBindingOnErrorCode(); 057 } 058 boolean validation = config.isClientRequestValidation(); 059 if (definition.getClientRequestValidation() != null) { 060 validation = definition.getClientRequestValidation(); 061 } 062 063 // cors headers 064 Map<String, String> corsHeaders = config.getCorsHeaders(); 065 066 if (mode == null || "off".equals(mode)) { 067 // binding mode is off, so create a off mode binding processor 068 return new RestBindingAdvice(context, null, null, null, null, definition.getConsumes(), definition.getProduces(), mode, skip, validation, cors, corsHeaders, 069 definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, 070 definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); 071 } 072 073 // setup json data format 074 DataFormat json = null; 075 DataFormat outJson = null; 076 if (mode.contains("json") || "auto".equals(mode)) { 077 String name = config.getJsonDataFormat(); 078 if (name != null) { 079 // must only be a name, not refer to an existing instance 080 Object instance = context.getRegistry().lookupByName(name); 081 if (instance != null) { 082 throw new IllegalArgumentException("JsonDataFormat name: " + name + " must not be an existing bean instance from the registry"); 083 } 084 } else { 085 name = "json-jackson"; 086 } 087 // this will create a new instance as the name was not already 088 // pre-created 089 json = context.resolveDataFormat(name); 090 outJson = context.resolveDataFormat(name); 091 092 if (json != null) { 093 Class<?> clazz = null; 094 String type = definition.getType(); 095 if (type != null) { 096 String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type; 097 clazz = context.getClassResolver().resolveMandatoryClass(typeName); 098 } 099 if (clazz != null) { 100 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, json, "unmarshalType", clazz); 101 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, json, "useList", type.endsWith("[]")); 102 } 103 setAdditionalConfiguration(config, context, json, "json.in."); 104 105 Class<?> outClazz = null; 106 String outType = definition.getOutType(); 107 if (outType != null) { 108 String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType; 109 outClazz = context.getClassResolver().resolveMandatoryClass(typeName); 110 } 111 if (outClazz != null) { 112 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJson, "unmarshalType", outClazz); 113 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJson, "useList", outType.endsWith("[]")); 114 } 115 setAdditionalConfiguration(config, context, outJson, "json.out."); 116 } 117 } 118 119 // setup xml data format 120 DataFormat jaxb = null; 121 DataFormat outJaxb = null; 122 if (mode.contains("xml") || "auto".equals(mode)) { 123 String name = config.getXmlDataFormat(); 124 if (name != null) { 125 // must only be a name, not refer to an existing instance 126 Object instance = context.getRegistry().lookupByName(name); 127 if (instance != null) { 128 throw new IllegalArgumentException("XmlDataFormat name: " + name + " must not be an existing bean instance from the registry"); 129 } 130 } else { 131 name = "jaxb"; 132 } 133 // this will create a new instance as the name was not already 134 // pre-created 135 jaxb = context.resolveDataFormat(name); 136 outJaxb = context.resolveDataFormat(name); 137 138 // is xml binding required? 139 if (mode.contains("xml") && jaxb == null) { 140 throw new IllegalArgumentException("XML DataFormat " + name + " not found."); 141 } 142 143 if (jaxb != null) { 144 Class<?> clazz = null; 145 String type = definition.getType(); 146 if (type != null) { 147 String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type; 148 clazz = context.getClassResolver().resolveMandatoryClass(typeName); 149 } 150 if (clazz != null) { 151 JAXBContext jc = JAXBContext.newInstance(clazz); 152 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, jaxb, "context", jc); 153 } 154 setAdditionalConfiguration(config, context, jaxb, "xml.in."); 155 156 Class<?> outClazz = null; 157 String outType = definition.getOutType(); 158 if (outType != null) { 159 String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType; 160 outClazz = context.getClassResolver().resolveMandatoryClass(typeName); 161 } 162 if (outClazz != null) { 163 JAXBContext jc = JAXBContext.newInstance(outClazz); 164 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJaxb, "context", jc); 165 } else if (clazz != null) { 166 // fallback and use the context from the input 167 JAXBContext jc = JAXBContext.newInstance(clazz); 168 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJaxb, "context", jc); 169 } 170 setAdditionalConfiguration(config, context, outJaxb, "xml.out."); 171 } 172 } 173 174 return new RestBindingAdvice(context, json, jaxb, outJson, outJaxb, definition.getConsumes(), definition.getProduces(), mode, skip, validation, cors, corsHeaders, 175 definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, 176 definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); 177 } 178 179 private void setAdditionalConfiguration(RestConfiguration config, CamelContext context, DataFormat dataFormat, String prefix) throws Exception { 180 if (config.getDataFormatProperties() != null && !config.getDataFormatProperties().isEmpty()) { 181 // must use a copy as otherwise the options gets removed during 182 // introspection setProperties 183 Map<String, Object> copy = new HashMap<>(); 184 185 // filter keys on prefix 186 // - either its a known prefix and must match the prefix parameter 187 // - or its a common configuration that we should always use 188 for (Map.Entry<String, Object> entry : config.getDataFormatProperties().entrySet()) { 189 String key = entry.getKey(); 190 String copyKey; 191 boolean known = isKeyKnownPrefix(key); 192 if (known) { 193 // remove the prefix from the key to use 194 copyKey = key.substring(prefix.length()); 195 } else { 196 // use the key as is 197 copyKey = key; 198 } 199 if (!known || key.startsWith(prefix)) { 200 copy.put(copyKey, entry.getValue()); 201 } 202 } 203 204 PropertyBindingSupport.build().bind(context, dataFormat, copy); 205 } 206 } 207 208 private boolean isKeyKnownPrefix(String key) { 209 return key.startsWith("json.in.") || key.startsWith("json.out.") || key.startsWith("xml.in.") || key.startsWith("xml.out."); 210 } 211 212}