diff options
author | Robert Godfrey <rgodfrey@apache.org> | 2014-07-30 13:07:51 +0000 |
---|---|---|
committer | Robert Godfrey <rgodfrey@apache.org> | 2014-07-30 13:07:51 +0000 |
commit | 6fae60887199cdcd6b2db87996eb838b519cffcf (patch) | |
tree | cccd9c1efcec647b81d1c650c670eb18e70ecb56 | |
parent | 96e8753e5647100138b87ae27036e407a0cef818 (diff) | |
download | qpid-python-6fae60887199cdcd6b2db87996eb838b519cffcf.tar.gz |
QPID-5946 : [Java Broker] Add alternative KeyStore implementation that can use standard crt/pem rather than jks files
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1614652 13f79535-47bb-0310-9956-ffa450edef68
6 files changed, 679 insertions, 27 deletions
diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java new file mode 100644 index 0000000000..9563f98579 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security; + +import org.apache.qpid.server.model.DerivedAttribute; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.ManagedAttribute; +import org.apache.qpid.server.model.ManagedObject; + +@ManagedObject( category = false, type = "NonJavaKeyStore" ) +public interface NonJavaKeyStore<X extends NonJavaKeyStore<X>> extends KeyStore<X> +{ + + @ManagedAttribute( mandatory = true, secure = true ) + String getPrivateKeyUrl(); + + @ManagedAttribute( mandatory = true ) + String getCertificateUrl(); + + @ManagedAttribute + String getIntermediateCertificateUrl(); + + @DerivedAttribute + String getSubjectName(); + + @DerivedAttribute + public long getCertificateValidEnd(); + + @DerivedAttribute + public long getCertificateValidStart(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java new file mode 100644 index 0000000000..efcd40c638 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java @@ -0,0 +1,404 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.AccessControlException; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.xml.bind.DatatypeConverter; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.AbstractConfiguredObject; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.IntegrityViolationException; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.ManagedAttributeField; +import org.apache.qpid.server.model.ManagedObject; +import org.apache.qpid.server.model.ManagedObjectFactoryConstructor; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.util.urlstreamhandler.data.Handler; + +@ManagedObject( category = false ) +public class NonJavaKeyStoreImpl extends AbstractConfiguredObject<NonJavaKeyStoreImpl> implements NonJavaKeyStore<NonJavaKeyStoreImpl> +{ + private static final Logger LOGGER = Logger.getLogger(NonJavaKeyStoreImpl.class); + + private final Broker<?> _broker; + + @ManagedAttributeField( afterSet = "updateKeyManagers" ) + private String _privateKeyUrl; + @ManagedAttributeField( afterSet = "updateKeyManagers" ) + private String _certificateUrl; + @ManagedAttributeField( afterSet = "updateKeyManagers" ) + private String _intermediateCertificateUrl; + + private volatile KeyManager[] _keyManagers = new KeyManager[0]; + + private static final SecureRandom RANDOM = new SecureRandom(); + + static + { + Handler.register(); + } + + private X509Certificate _certificate; + + @ManagedObjectFactoryConstructor + public NonJavaKeyStoreImpl(final Map<String, Object> attributes, Broker<?> broker) + { + super(parentsMap(broker), attributes); + _broker = broker; + } + + @Override + public String getPrivateKeyUrl() + { + return _privateKeyUrl; + } + + @Override + public String getCertificateUrl() + { + return _certificateUrl; + } + + @Override + public String getIntermediateCertificateUrl() + { + return _intermediateCertificateUrl; + } + + @Override + public String getSubjectName() + { + if(_certificate != null) + { + try + { + String dn = _certificate.getSubjectX500Principal().getName(); + LdapName ldapDN = new LdapName(dn); + String name = dn; + for (Rdn rdn : ldapDN.getRdns()) + { + if (rdn.getType().equalsIgnoreCase("CN")) + { + name = String.valueOf(rdn.getValue()); + break; + } + } + return name; + } + catch (InvalidNameException e) + { + LOGGER.error("Error getting subject name from certificate"); + return null; + } + } + else + { + return null; + } + } + + @Override + public long getCertificateValidEnd() + { + return _certificate == null ? 0 : _certificate.getNotAfter().getTime(); + } + + @Override + public long getCertificateValidStart() + { + return _certificate == null ? 0 : _certificate.getNotBefore().getTime(); + } + + + @Override + public KeyManager[] getKeyManagers() throws GeneralSecurityException + { + + return _keyManagers; + } + + @Override + public void onValidate() + { + super.onValidate(); + validateKeyStoreAttributes(this); + } + + @Override + public State getState() + { + return State.ACTIVE; + } + + @Override + public Object getAttribute(String name) + { + if (KeyStore.STATE.equals(name)) + { + return getState(); + } + + return super.getAttribute(name); + } + + @Override + protected boolean setState(State desiredState) + { + if (desiredState == State.DELETED) + { + // verify that it is not in use + String storeName = getName(); + + Collection<Port> ports = new ArrayList<Port>(_broker.getPorts()); + for (Port port : ports) + { + if (port.getKeyStore() == this) + { + throw new IntegrityViolationException("Key store '" + + storeName + + "' can't be deleted as it is in use by a port:" + + port.getName()); + } + } + deleted(); + return true; + } + + return false; + } + + @Override + protected void authoriseSetDesiredState(State desiredState) throws AccessControlException + { + if (desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of key store is denied"); + } + } + } + + @Override + protected void authoriseSetAttributes(ConfiguredObject<?> modified, Set<String> attributes) + throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting key store attributes is denied"); + } + } + + @Override + protected void validateChange(final ConfiguredObject<?> proxyForValidation, final Set<String> changedAttributes) + { + super.validateChange(proxyForValidation, changedAttributes); + NonJavaKeyStore changedStore = (NonJavaKeyStore) proxyForValidation; + if (changedAttributes.contains(NAME) && !getName().equals(changedStore.getName())) + { + throw new IllegalConfigurationException("Changing the key store name is not allowed"); + } + validateKeyStoreAttributes(changedStore); + } + + private void validateKeyStoreAttributes(NonJavaKeyStore<?> keyStore) + { + try + { + getUrlFromString(keyStore.getPrivateKeyUrl()).openStream(); + getUrlFromString(keyStore.getCertificateUrl()).openStream(); + if(keyStore.getIntermediateCertificateUrl() != null) + { + getUrlFromString(keyStore.getIntermediateCertificateUrl()).openStream(); + } + } + catch (IOException e) + { + throw new IllegalArgumentException(e); + } + } + + @SuppressWarnings("unused") + private void updateKeyManagers() + { + try + { + if (_privateKeyUrl != null && _certificateUrl != null) + { + PrivateKey privateKey = readPrivateKey(getUrlFromString(_privateKeyUrl)); + X509Certificate[] certs = readCertificates(getUrlFromString(_certificateUrl)); + if(_intermediateCertificateUrl != null) + { + List<X509Certificate> allCerts = new ArrayList<>(Arrays.asList(certs)); + allCerts.addAll(Arrays.asList(readCertificates(getUrlFromString(_intermediateCertificateUrl)))); + certs = allCerts.toArray(new X509Certificate[allCerts.size()]); + } + + java.security.KeyStore inMemoryKeyStore = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType()); + + byte[] bytes = new byte[64]; + char[] chars = new char[64]; + RANDOM.nextBytes(bytes); + StandardCharsets.US_ASCII.decode(ByteBuffer.wrap(bytes)).get(chars); + inMemoryKeyStore.load(null, chars); + inMemoryKeyStore.setKeyEntry("1", privateKey, chars, certs); + + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(inMemoryKeyStore, chars); + _keyManagers = kmf.getKeyManagers(); + _certificate = certs[0]; + } + + } + catch (IOException | GeneralSecurityException e) + { + LOGGER.error("Error attempting to create KeyStore from private key and certificates", e); + _keyManagers = new KeyManager[0]; + } + } + + private URL getUrlFromString(String urlString) throws MalformedURLException + { + URL url; + + try + { + url = new URL(urlString); + } + catch (MalformedURLException e) + { + File file = new File(urlString); + url = file.toURI().toURL(); + + } + return url; + } + + public static X509Certificate[] readCertificates(URL certFile) + throws IOException, GeneralSecurityException + { + List<X509Certificate> crt = new ArrayList<>(); + try (InputStream is = certFile.openStream()) + { + do + { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + crt.add( (X509Certificate) cf.generateCertificate(is)); + } while(is.available() != 0); + } + catch(CertificateException e) + { + if(crt.isEmpty()) + { + throw e; + } + } + return crt.toArray(new X509Certificate[crt.size()]); + } + + private static PrivateKey readPrivateKey(final URL url) + throws IOException, GeneralSecurityException + { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (InputStream urlStream = url.openStream()) + { + byte[] tmp = new byte[1024]; + int read; + while((read = urlStream.read(tmp)) != -1) + { + buffer.write(tmp,0,read); + } + } + + byte[] content = buffer.toByteArray(); + String contentAsString = new String(content, StandardCharsets.US_ASCII); + if(contentAsString.contains("-----BEGIN ") && contentAsString.contains(" PRIVATE KEY-----")) + { + BufferedReader lineReader = new BufferedReader(new StringReader(contentAsString)); + + String line; + do + { + line = lineReader.readLine(); + } while(line != null && !(line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----"))); + + if(line != null) + { + StringBuilder keyBuilder = new StringBuilder(); + + while((line = lineReader.readLine()) != null) + { + if(line.startsWith("-----END ") && line.endsWith(" PRIVATE KEY-----")) + { + break; + } + keyBuilder.append(line); + } + + content = DatatypeConverter.parseBase64Binary(keyBuilder.toString()); + } + } + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(content); + KeyFactory kf = KeyFactory.getInstance("RSA"); + PrivateKey key = kf.generatePrivate(keySpec); + return key; + } + + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java new file mode 100644 index 0000000000..fb0ab4f696 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java @@ -0,0 +1,148 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.util.urlstreamhandler.data; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.net.URLStreamHandler; +import java.nio.charset.StandardCharsets; + +import javax.xml.bind.DatatypeConverter; + +public class Handler extends URLStreamHandler +{ + public static final String PROTOCOL_HANDLER_PROPERTY = "java.protocol.handler.pkgs"; + private static boolean _registered; + + @Override + protected URLConnection openConnection(final URL u) throws IOException + { + return new DataUrlConnection(u); + } + + public synchronized static void register() + { + if(!_registered) + { + String registeredPackages = System.getProperty(PROTOCOL_HANDLER_PROPERTY); + String thisPackage = Handler.class.getPackage().getName(); + String packageToRegister = thisPackage.substring(0, thisPackage.lastIndexOf('.') ); + System.setProperty(PROTOCOL_HANDLER_PROPERTY, + registeredPackages == null + ? packageToRegister + : packageToRegister + "|" + registeredPackages); + + _registered = true; + } + + + + } + + private static class DataUrlConnection extends URLConnection + { + private final byte[] _content; + private final String _contentType; + private final boolean _base64; + + public DataUrlConnection(final URL u) throws IOException + { + super(u); + String externalForm = u.toExternalForm(); + if(externalForm.startsWith("data:")) + { + String[] parts = externalForm.substring(5).split(",",2); + _base64 = parts[0].endsWith(";base64"); + if(_base64) + { + _content = DatatypeConverter.parseBase64Binary(parts[1]); + } + else + { + try + { + _content = URLDecoder.decode(parts[1], StandardCharsets.US_ASCII.name()).getBytes(StandardCharsets.US_ASCII); + } + catch (UnsupportedEncodingException e) + { + throw new IOException(e); + } + } + String mediaType = (_base64 + ? parts[0].substring(0,parts[0].length()-";base64".length()) + : parts[0]).split(";")[0]; + + _contentType = "".equals(mediaType) ? "text/plain" : mediaType; + } + else + { + throw new MalformedURLException("'"+externalForm+"' does not start with 'data:'"); + } + } + + + + @Override + public void connect() throws IOException + { + + } + + @Override + public int getContentLength() + { + return _content.length; + } + + @Override + public String getContentType() + { + return _contentType; + } + + @Override + public String getContentEncoding() + { + return _base64 ? "base64" : null; + } + + @Override + public InputStream getInputStream() throws IOException + { + return new ByteArrayInputStream(_content); + } + } + + public static void main(String[] args) throws IOException + { + register(); + URL url = new URL("data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7"); + InputStream is = url.openStream(); + url = new URL("data:,A%20brief%20note"); + is = url.openStream(); + } +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java index 86a054ef31..ece4ccca4f 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java @@ -34,6 +34,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import javax.servlet.DispatcherType; +import javax.servlet.MultipartConfigElement; import org.apache.log4j.Logger; import org.eclipse.jetty.server.Connector; @@ -385,8 +386,14 @@ public class HttpManagement extends AbstractPluginAdapter<HttpManagement> implem private void addRestServlet(ServletContextHandler root, String name, Class<? extends ConfiguredObject>... hierarchy) { - root.addServlet(new ServletHolder(name, new RestServlet(hierarchy)), "/api/latest/" + name + "/*"); - root.addServlet(new ServletHolder(name, new RestServlet(hierarchy)), "/api/v" + BrokerModel.MODEL_MAJOR_VERSION + "/" + name + "/*"); + ServletHolder servletHolder = new ServletHolder(name, new RestServlet(hierarchy)); + servletHolder.getRegistration().setMultipartConfig( + new MultipartConfigElement("", + getContextValue(Long.class, MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME), + -1l, + getContextValue(Integer.class, MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME))); + root.addServlet(servletHolder, "/api/latest/" + name + "/*"); + root.addServlet(servletHolder, "/api/v" + BrokerModel.MODEL_MAJOR_VERSION + "/" + name + "/*"); } private void logOperationalListenMessages(Server server) diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java index c054ffe405..aff9f3a7e2 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java @@ -24,6 +24,7 @@ import java.net.SocketAddress; import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.ManagedAttribute; +import org.apache.qpid.server.model.ManagedContextDefault; import org.apache.qpid.server.model.Plugin; public interface HttpManagementConfiguration<X extends HttpManagementConfiguration<X>> extends Plugin<X> @@ -43,5 +44,9 @@ public interface HttpManagementConfiguration<X extends HttpManagementConfigurati @ManagedAttribute( defaultValue = "600" ) public int getSessionTimeout(); + String MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME = "maxHttpFileUploadSize"; + @ManagedContextDefault( name = MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME) + static final long DEFAULT_MAX_UPLOAD_SIZE = 100 * 1024; + AuthenticationProvider getAuthenticationProvider(SocketAddress localAddress); } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java index 87a4b6fb1a..8df234d24d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -36,6 +36,8 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; +import javax.xml.bind.DatatypeConverter; import org.apache.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; @@ -43,6 +45,7 @@ import org.codehaus.jackson.map.SerializationConfig; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.util.urlstreamhandler.data.Handler; public class RestServlet extends AbstractServlet { @@ -85,6 +88,7 @@ public class RestServlet extends AbstractServlet { doInitialization(); } + Handler.register(); } @SuppressWarnings("unchecked") @@ -342,23 +346,56 @@ public class RestServlet extends AbstractServlet { response.setContentType("application/json"); - ObjectMapper mapper = new ObjectMapper(); - @SuppressWarnings("unchecked") - Map<String,Object> providedObject = mapper.readValue(request.getInputStream(), LinkedHashMap.class); - - List<String> names = new ArrayList<String>(); String[] pathInfoElements = getPathInfoElements(request); - if(pathInfoElements != null ) + if (pathInfoElements != null) { - if(pathInfoElements.length != _hierarchy.length) + if (pathInfoElements.length != _hierarchy.length) { throw new IllegalArgumentException("Path to object to create must be fully specified. " - + "Found " + names + " of size " + names.size() + " expecting " + _hierarchy.length); + + "Found " + + names + + " of size " + + names.size() + + " expecting " + + _hierarchy.length); } names.addAll(Arrays.asList(pathInfoElements)); } + Map<String, Object> providedObject; + + ArrayList<String> headers = Collections.list(request.getHeaderNames()); + ObjectMapper mapper = new ObjectMapper(); + + if(headers.contains("Content-Type") && request.getHeader("Content-Type").startsWith("multipart/form-data")) + { + providedObject = new HashMap<>(); + Map<String,String> fileUploads = new HashMap<>(); + Collection<Part> parts = request.getParts(); + for(Part part : parts) + { + if("data".equals(part.getName()) && "application/json".equals(part.getContentType())) + { + providedObject = mapper.readValue(part.getInputStream(), LinkedHashMap.class); + } + else + { + byte[] data = new byte[(int) part.getSize()]; + part.getInputStream().read(data); + StringBuilder inlineURL = new StringBuilder("data:;base64,"); + inlineURL.append(DatatypeConverter.printBase64Binary(data)); + fileUploads.put(part.getName(),inlineURL.toString()); + } + } + providedObject.putAll(fileUploads); + } + else + { + + providedObject = mapper.readValue(request.getInputStream(), LinkedHashMap.class); + } + if (names.isEmpty()) { if (_hierarchy.length == 0) @@ -368,7 +405,7 @@ public class RestServlet extends AbstractServlet doUpdate(getBroker(), providedObject); response.setStatus(HttpServletResponse.SC_OK); } - catch(RuntimeException e) + catch (RuntimeException e) { setResponseStatus(response, e); } @@ -380,24 +417,24 @@ public class RestServlet extends AbstractServlet } } - providedObject.put("name", names.get(names.size()-1)); + providedObject.put("name", names.get(names.size() - 1)); @SuppressWarnings("unchecked") Collection<ConfiguredObject>[] objects = new Collection[_hierarchy.length]; - if(_hierarchy.length == 1) + if (_hierarchy.length == 1) { createOrUpdate(providedObject, _hierarchy[0], getBroker(), null, response); } else { - for(int i = 0; i < _hierarchy.length-1; i++) + for (int i = 0; i < _hierarchy.length - 1; i++) { objects[i] = new HashSet<ConfiguredObject>(); - if(i == 0) + if (i == 0) { - for(ConfiguredObject object : getBroker().getChildren(_hierarchy[0])) + for (ConfiguredObject object : getBroker().getChildren(_hierarchy[0])) { - if(object.getName().equals(names.get(0))) + if (object.getName().equals(names.get(0))) { objects[0].add(object); break; @@ -406,15 +443,15 @@ public class RestServlet extends AbstractServlet } else { - for(int j = i-1; j >=0; j--) + for (int j = i - 1; j >= 0; j--) { - if(getBroker().getModel().getChildTypes(_hierarchy[j]).contains(_hierarchy[i])) + if (getBroker().getModel().getChildTypes(_hierarchy[j]).contains(_hierarchy[i])) { - for(ConfiguredObject<?> parent : objects[j]) + for (ConfiguredObject<?> parent : objects[j]) { - for(ConfiguredObject<?> object : parent.getChildren(_hierarchy[i])) + for (ConfiguredObject<?> object : parent.getChildren(_hierarchy[i])) { - if(object.getName().equals(names.get(i))) + if (object.getName().equals(names.get(i))) { objects[i].add(object); } @@ -428,19 +465,20 @@ public class RestServlet extends AbstractServlet } List<ConfiguredObject> parents = new ArrayList<ConfiguredObject>(); Class<? extends ConfiguredObject> objClass = getConfiguredClass(); - Collection<Class<? extends ConfiguredObject>> parentClasses = getBroker().getModel().getParentTypes(objClass); - for(int i = _hierarchy.length-2; i >=0 ; i--) + Collection<Class<? extends ConfiguredObject>> parentClasses = + getBroker().getModel().getParentTypes(objClass); + for (int i = _hierarchy.length - 2; i >= 0; i--) { - if(parentClasses.contains(_hierarchy[i])) + if (parentClasses.contains(_hierarchy[i])) { - if(objects[i].size() == 1) + if (objects[i].size() == 1) { parents.add(objects[i].iterator().next()); } else { throw new IllegalArgumentException("Cannot deduce parent of class " - + _hierarchy[i].getSimpleName()); + + _hierarchy[i].getSimpleName()); } } @@ -450,6 +488,7 @@ public class RestServlet extends AbstractServlet createOrUpdate(providedObject, objClass, theParent, otherParents, response); } + } private void createOrUpdate(Map<String, Object> providedObject, Class<? extends ConfiguredObject> objClass, |