This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch smb2 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 392100145037299c1899217ae197cbf2ba62242f Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Wed Jan 1 15:09:34 2025 +0100 CAMEL-21352: camel-smb - Base on camel-file. Work in progress. --- .../apache/camel/component/smb2/Smb2Component.java | 67 +++++++ .../camel/component/smb2/Smb2Configuration.java | 128 +++++++++++++ .../apache/camel/component/smb2/Smb2Constants.java | 26 +++ .../apache/camel/component/smb2/Smb2Endpoint.java | 91 +++++++++ .../org/apache/camel/component/smb2/Smb2File.java | 85 +++++++++ .../camel/component/smb2/Smb2FileOperations.java | 58 ++++++ .../camel/component/smb2/Smb2Operations.java | 204 +++++++++++++++++++++ 7 files changed, 659 insertions(+) diff --git a/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Component.java b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Component.java new file mode 100644 index 00000000000..a54ce096949 --- /dev/null +++ b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Component.java @@ -0,0 +1,67 @@ +/* + * 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.camel.component.smb2; + +import java.net.URI; +import java.util.Map; + +import com.hierynomus.smbj.share.File; +import org.apache.camel.CamelContext; +import org.apache.camel.component.file.GenericFileComponent; +import org.apache.camel.component.file.GenericFileEndpoint; +import org.apache.camel.spi.annotations.Component; +import org.apache.camel.util.StringHelper; + +@Component("smb2") +public class Smb2Component extends GenericFileComponent<File> { + + public Smb2Component() { + } + + public Smb2Component(CamelContext context) { + super(context); + } + + @Override + protected GenericFileEndpoint<File> buildFileEndpoint(String uri, String remaining, Map<String, Object> parameters) + throws Exception { + String baseUri = getBaseUri(uri); + + // lets make sure we create a new configuration as each endpoint can + // customize its own version + // must pass on baseUri to the configuration (see above) + Smb2Configuration config = new Smb2Configuration(new URI(baseUri)); + + Smb2Endpoint answer = new Smb2Endpoint(uri, this, config); + return answer; + } + + @Override + protected void afterPropertiesSet(GenericFileEndpoint<File> endpoint) throws Exception { + // noop + } + + /** + * Get the base uri part before the options as they can be non URI valid such as the expression using $ chars and + * the URI constructor will regard $ as an illegal character, and we don't want to enforce end users to escape the $ + * for the expression (file language) + */ + protected String getBaseUri(String uri) { + return StringHelper.before(uri, "?", uri); + } + +} diff --git a/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Configuration.java b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Configuration.java new file mode 100644 index 00000000000..dbea729944f --- /dev/null +++ b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Configuration.java @@ -0,0 +1,128 @@ +/* + * 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.camel.component.smb2; + +import java.net.URI; + +import org.apache.camel.component.file.GenericFileConfiguration; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; +import org.apache.camel.spi.UriPath; + +@UriParams +public class Smb2Configuration extends GenericFileConfiguration { + + // component name is implied as the protocol + private String protocol; + + @UriPath + @Metadata(required = true) + private String hostname; + @UriPath(defaultValue = "445") + private int port; + @UriPath + @Metadata(required = true) + private String shareName; + @UriParam(label = "security", description = "The username required to access the share", secret = true) + private String username; + @UriParam(label = "security", description = "The password to access the share", secret = true) + private String password; + @UriParam(label = "security", description = "The user domain") + private String domain; + + public Smb2Configuration() { + setProtocol("smb"); + } + + public Smb2Configuration(URI uri) { + super.configure(uri); + setProtocol(uri.getScheme()); + setHostname(uri.getHost()); + if (uri.getPort() > 0) { + setPort(uri.getPort()); + } + } + + public String getProtocol() { + return protocol; + } + + /** + * The smb protocol to use + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getHostname() { + return hostname; + } + + /** + * The share hostname or IP address + */ + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public int getPort() { + return port; + } + + /** + * The share port number + */ + public void setPort(int port) { + this.port = port; + } + + public String getShareName() { + return shareName; + } + + /** + * The name of the share to connect to. + */ + public void setShareName(String shareName) { + this.shareName = shareName; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } +} diff --git a/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Constants.java b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Constants.java new file mode 100644 index 00000000000..bb184a8c0e4 --- /dev/null +++ b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Constants.java @@ -0,0 +1,26 @@ +/* + * 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.camel.component.smb2; + +import org.apache.camel.spi.Metadata; + +public class Smb2Constants { + + @Metadata(description = "The remote hostname.", javaType = "String") + public static final String FILE_HOST = "CamelFileHost"; + +} diff --git a/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Endpoint.java b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Endpoint.java new file mode 100644 index 00000000000..38c52a84170 --- /dev/null +++ b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Endpoint.java @@ -0,0 +1,91 @@ +/* + * 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.camel.component.smb2; + +import java.util.Map; + +import com.hierynomus.smbj.share.File; +import org.apache.camel.Exchange; +import org.apache.camel.component.file.GenericFile; +import org.apache.camel.component.file.GenericFileEndpoint; +import org.apache.camel.spi.EndpointServiceLocation; +import org.apache.camel.spi.UriParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Smb2Endpoint extends GenericFileEndpoint<File> implements EndpointServiceLocation { + + private static final Logger LOG = LoggerFactory.getLogger(Smb2Endpoint.class); + + @UriParam + protected Smb2Configuration configuration; + + protected Smb2Endpoint(String uri, Smb2Component component, Smb2Configuration configuration) { + super(uri, component); + this.configuration = configuration; + } + + @Override + public boolean isSingletonProducer() { + // this producer is stateful because the smb file operations is not + // thread safe + return false; + } + + @Override + public String getScheme() { + return "smb2"; + } + + @Override + public char getFileSeparator() { + return '/'; + } + + @Override + public boolean isAbsolute(String name) { + return name.startsWith("/"); + } + + @Override + public String getServiceUrl() { + return configuration.getProtocol() + ":" + configuration.getHostname() + ":" + configuration.getPort(); + } + + @Override + public String getServiceProtocol() { + return configuration.getProtocol(); + } + + @Override + public Map<String, String> getServiceMetadata() { + if (configuration.getUsername() != null) { + return Map.of("username", configuration.getUsername()); + } + return null; + } + + @Override + public Exchange createExchange(GenericFile<File> file) { + Exchange answer = super.createExchange(); + if (file != null) { + file.bindToExchange(answer); + } + return answer; + } + +} diff --git a/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2File.java b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2File.java new file mode 100644 index 00000000000..d0868e1a340 --- /dev/null +++ b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2File.java @@ -0,0 +1,85 @@ +/* + * 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.camel.component.smb2; + +import com.hierynomus.smbj.share.File; +import org.apache.camel.component.file.GenericFile; +import org.apache.camel.component.file.GenericFileMessage; + +public class Smb2File extends GenericFile<File> implements Cloneable { + + private String hostname; + + /** + * Populates the {@link GenericFileMessage} relevant headers + * + * @param message the message to populate with headers + */ + public void populateHeaders(GenericFileMessage message) { + if (message != null) { + // because there is not probeContentType option + // in other file based components, false may be passed + // as the second argument. + super.populateHeaders(message, false); + message.setHeader(Smb2Constants.FILE_HOST, getHostname()); + } + } + + @Override + public void populateHeaders(GenericFileMessage message, boolean isProbeContentTypeFromEndpoint) { + populateHeaders(message); + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + @Override + public char getFileSeparator() { + // always use / as separator for FTP + return '/'; + } + + @Override + protected boolean isAbsolute(String name) { + if (!name.isEmpty()) { + return name.charAt(0) == '/' || name.charAt(0) == '\\'; + } + return false; + } + + @Override + protected String normalizePath(String name) { + return name; + } + + @Override + public void copyFromPopulateAdditional(GenericFile source, GenericFile result) { + Smb2File remoteSource = (Smb2File) source; + Smb2File remoteResult = (Smb2File) result; + remoteResult.setHostname(remoteSource.getHostname()); + } + + @Override + public String toString() { + return "Smb2File[" + (isAbsolute() ? getAbsoluteFilePath() : getRelativeFilePath()) + "]"; + } +} diff --git a/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2FileOperations.java b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2FileOperations.java new file mode 100644 index 00000000000..316aec137b0 --- /dev/null +++ b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2FileOperations.java @@ -0,0 +1,58 @@ +/* + * 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.camel.component.smb2; + +import com.hierynomus.smbj.share.File; +import org.apache.camel.Exchange; +import org.apache.camel.component.file.GenericFileOperationFailedException; +import org.apache.camel.component.file.GenericFileOperations; + +public interface Smb2FileOperations extends GenericFileOperations<File> { + + /** + * Connects to the remote server + * + * @param configuration configuration + * @param exchange the exchange that trigger the connect (if any) + * @return <tt>true</tt> if connected + * @throws GenericFileOperationFailedException can be thrown + */ + boolean connect(Smb2Configuration configuration, Exchange exchange) throws GenericFileOperationFailedException; + + /** + * Returns whether we are connected to the remote server or not + * + * @return <tt>true</tt> if connected, <tt>false</tt> if not + * @throws GenericFileOperationFailedException can be thrown + */ + boolean isConnected() throws GenericFileOperationFailedException; + + /** + * Disconnects from the remote server + * + * @throws GenericFileOperationFailedException can be thrown + */ + void disconnect() throws GenericFileOperationFailedException; + + /** + * Forces a hard disconnect from the remote server and cause the client to be re-created on next poll. + * + * @throws GenericFileOperationFailedException can be thrown + */ + void forceDisconnect() throws GenericFileOperationFailedException; + +} diff --git a/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Operations.java b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Operations.java new file mode 100644 index 00000000000..4df1e9f1e64 --- /dev/null +++ b/components/camel-smb/src/main/java/org/apache/camel/component/smb2/Smb2Operations.java @@ -0,0 +1,204 @@ +/* + * 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.camel.component.smb2; + +import java.io.IOException; + +import com.hierynomus.smbj.SMBClient; +import com.hierynomus.smbj.auth.AuthenticationContext; +import com.hierynomus.smbj.connection.Connection; +import com.hierynomus.smbj.session.Session; +import com.hierynomus.smbj.share.DiskShare; +import com.hierynomus.smbj.share.File; +import com.hierynomus.smbj.utils.SmbFiles; +import org.apache.camel.Exchange; +import org.apache.camel.component.file.GenericFile; +import org.apache.camel.component.file.GenericFileEndpoint; +import org.apache.camel.component.file.GenericFileOperationFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Smb2Operations implements Smb2FileOperations { + + private static final Logger LOG = LoggerFactory.getLogger(Smb2Operations.class); + + private final Smb2Configuration configuration; + private Smb2Endpoint endpoint; + private boolean loggedIn; + private Session session; + private DiskShare share; + private SMBClient smbClient; + + public Smb2Operations(Smb2Configuration configuration) { + this.configuration = configuration; + } + + @Override + public boolean connect(Smb2Configuration configuration, Exchange exchange) throws GenericFileOperationFailedException { + if (smbClient == null) { + smbClient = new SMBClient(); + } + try { + connectIfNecessary(); + } catch (IOException e) { + throw new GenericFileOperationFailedException( + "Cannot connect to: " + configuration.getHostname() + ":" + configuration.getPort() + " due to: " + + e.getMessage(), + e); + } + return false; + } + + protected void connectIfNecessary() throws IOException { + Connection connection = smbClient.connect(configuration.getHostname(), configuration.getPort()); + + if (!loggedIn || !isConnected()) { + LOG.debug("Not already connected/logged in. Connecting to: {}:{}", configuration.getHostname(), + configuration.getPort()); + + AuthenticationContext ac = new AuthenticationContext( + configuration.getUsername(), + configuration.getPassword().toCharArray(), + configuration.getDomain()); + session = connection.authenticate(ac); + + share = (DiskShare) session.connectShare(configuration.getShareName()); + + LOG.debug("Connected and logged in to: {}:{}", configuration.getHostname(), configuration.getPort()); + loggedIn = true; + } + } + + @Override + public boolean isConnected() throws GenericFileOperationFailedException { + if (share != null) { + return share.isConnected(); + } + return false; + } + + @Override + public void disconnect() throws GenericFileOperationFailedException { + loggedIn = false; + if (share != null) { + try { + share.close(); + } catch (IOException e) { + // ignore + } + share = null; + } + if (session != null) { + try { + session.close(); + } catch (Exception e) { + // ignore + } + session = null; + } + } + + @Override + public void forceDisconnect() throws GenericFileOperationFailedException { + disconnect(); + if (smbClient != null) { + smbClient.close(); + } + smbClient = null; + } + + @Override + public GenericFile<File> newGenericFile() { + return new Smb2File(); + } + + @Override + public void setEndpoint(GenericFileEndpoint<File> endpoint) { + this.endpoint = (Smb2Endpoint) endpoint; + } + + @Override + public boolean deleteFile(String name) throws GenericFileOperationFailedException { + share.rm(name); + return true; + } + + @Override + public boolean existsFile(String name) throws GenericFileOperationFailedException { + return share.fileExists(name); + } + + @Override + public boolean renameFile(String from, String to) throws GenericFileOperationFailedException { + return false; + } + + @Override + public boolean buildDirectory(String directory, boolean absolute) throws GenericFileOperationFailedException { + SmbFiles files = new SmbFiles(); + files.mkdirs(share, normalize(directory)); + return true; + } + + @Override + public boolean retrieveFile(String name, Exchange exchange, long size) throws GenericFileOperationFailedException { + return true; + } + + @Override + public void releaseRetrievedFileResources(Exchange exchange) throws GenericFileOperationFailedException { + // noop + } + + @Override + public boolean storeFile(String name, Exchange exchange, long size) throws GenericFileOperationFailedException { + return false; + } + + @Override + public String getCurrentDirectory() throws GenericFileOperationFailedException { + return null; // noop + } + + @Override + public void changeCurrentDirectory(String path) throws GenericFileOperationFailedException { + // noop + } + + @Override + public void changeToParentDirectory() throws GenericFileOperationFailedException { + // noop + } + + @Override + public File[] listFiles() throws GenericFileOperationFailedException { + return null; // noop + } + + @Override + public File[] listFiles(String path) throws GenericFileOperationFailedException { + return null; //noop + } + + /* + * Normalize changes separators for smb + */ + private String normalize(String file) { + return file.replace('\\', endpoint.getFileSeparator()); + } + +}