Skip to content

Commit

Permalink
Merge branch 'private-release/v2.0.1' into zowe-release/v2.0.1
Browse files Browse the repository at this point in the history
Signed-off-by: Uladzislau <[email protected]>
  • Loading branch information
KUGDev committed Nov 5, 2024
2 parents d466545 + 5f3629b commit 0aff128
Show file tree
Hide file tree
Showing 21 changed files with 587 additions and 140 deletions.
152 changes: 78 additions & 74 deletions src/main/kotlin/org/zowe/explorer/api/ZosmfApiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@
package org.zowe.explorer.api

import com.google.gson.GsonBuilder
import com.intellij.util.net.ssl.CertificateManager
import org.zowe.kotlinsdk.buildApi
import org.zowe.explorer.config.connect.ConnectionConfig
import org.zowe.kotlinsdk.buildApiWithBytesConverter
import okhttp3.ConnectionPool
import okhttp3.ConnectionSpec
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import org.zowe.kotlinsdk.buildApi
import org.zowe.kotlinsdk.buildApiWithBytesConverter
import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
import javax.net.ssl.*

/**
* Class that implements z/OSMF API for sending requests.
Expand Down Expand Up @@ -101,44 +100,8 @@ class ZosmfApiImpl : ZosmfApi {
}
}

private val gsonFactory = GsonConverterFactory.create(GsonBuilder().create())
private val scalarsConverterFactory = ScalarsConverterFactory.create()

/**
* Connection pool is initialized and the connection parameters are set.
*/
private fun OkHttpClient.Builder.addThreadPool(): OkHttpClient.Builder {
readTimeout(5, TimeUnit.MINUTES)
connectTimeout(5, TimeUnit.MINUTES)
connectionPool(ConnectionPool(100, 5, TimeUnit.MINUTES))
dispatcher(Dispatcher().apply {
maxRequests = 100
maxRequestsPerHost = 100
})
return this
}

val unsafeOkHttpClient by lazy { buildUnsafeClient() }
val safeOkHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.setupClient()
.build()
}

/**
* Setups http client. Adds the necessary headers. Configures connection specs.
*/
private fun OkHttpClient.Builder.setupClient(): OkHttpClient.Builder {
return addThreadPool()
.addInterceptor {
it.request().newBuilder().addHeader("X-CSRF-ZOSMF-HEADER", "").build().let { request ->
it.proceed(request)
}
}.connectionSpecs(mutableListOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT))
}

/**
* Returns [OkHttpClient] depending on whether self-signed certificates are allowed or not.
* Returns [OkHttpClient] depending on whether self-signed certificates are allowed or not
* @param isAllowSelfSigned whether to allow self-signed certificates.
* @return safe or unsafe [OkHttpClient] object.
*/
Expand All @@ -150,43 +113,84 @@ private fun getOkHttpClient(isAllowSelfSigned: Boolean): OkHttpClient {
}
}

private val unsafeOkHttpClient by lazy { buildUnsafeClient() }
private val safeOkHttpClient by lazy { buildSafeClient() }

/**
* Method for building an unsafe http client that allows the use of self-signed certificates.
* @throws RuntimeException if timeout is exceeded.
* @return unsafe [OkHttpClient] object.
* Build an unsafe HTTP client that will allow the use of self-signed certificates
* @return unsafe [OkHttpClient] object
*/
private fun buildUnsafeClient(): OkHttpClient {
return try {
val trustAllCerts: Array<TrustManager> = arrayOf(
object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(
chain: Array<X509Certificate?>?,
authType: String?
) {
}
val trustAllCerts: Array<TrustManager> = arrayOf(
object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(
chain: Array<X509Certificate?>?,
authType: String?
) {
}

@Throws(CertificateException::class)
override fun checkServerTrusted(
chain: Array<X509Certificate?>?,
authType: String?
) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(
chain: Array<X509Certificate?>?,
authType: String?
) {
}

override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
}
)
val sslContext = SSLContext.getInstance("TLSv1.2")
sslContext.init(null, trustAllCerts, SecureRandom())
val sslSocketFactory = sslContext.socketFactory
return OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
.setupClient()
.build()
}

/**
* Build a safe HTTP client that will reuse all allowed secured trusted certificates across the client's system
* (as well as those uploaded into IntelliJ's server certificates store)
* @return safe [OkHttpClient] object
*/
private fun buildSafeClient(): OkHttpClient {
val trustManager = CertificateManager.getInstance().trustManager
val sslContext = CertificateManager.getInstance().sslContext
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustManager)
.setupClient()
.build()
}

/** Set up an HTTP client. Adds the necessary headers. Configures connection specs */
private fun OkHttpClient.Builder.setupClient(): OkHttpClient.Builder {
return addThreadPool()
.addInterceptor {
it.request()
.newBuilder()
.addHeader("X-CSRF-ZOSMF-HEADER", "")
.build()
.let { request ->
it.proceed(request)
}
}
.connectionSpecs(
mutableListOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT)
)
val sslContext = SSLContext.getInstance("TLSv1.2")
sslContext.init(null, trustAllCerts, SecureRandom())
val sslSocketFactory = sslContext.socketFactory
val builder = OkHttpClient.Builder()
builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
builder.hostnameVerifier { _, _ -> true }
builder.setupClient()
builder.build()
} catch (e: Exception) {
throw RuntimeException(e)
}
}

/** Connection pool is initialized and the connection parameters are set */
private fun OkHttpClient.Builder.addThreadPool(): OkHttpClient.Builder {
readTimeout(5, TimeUnit.MINUTES)
connectTimeout(5, TimeUnit.MINUTES)
connectionPool(ConnectionPool(100, 5, TimeUnit.MINUTES))
dispatcher(Dispatcher().apply {
maxRequests = 100
maxRequestsPerHost = 100
})
return this
}
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ class ConnectionDialog(
textField()
.bindText(state::connectionUrl)
.validationOnApply {
it.text = it.text.trim().removeTrailingSlashes()
it.text = it.text.trim()
validateForBlank(it) ?: validateZosmfUrl(it)
}
.also { urlTextField = it.component }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ data class RemoteUssAttributes(
val gid: Long? = null,
val groupId: String? = null,
val modificationTime: String? = null,
val symlinkTarget: String? = null
val symlinkTarget: String? = null,
var charset: Charset = DEFAULT_BINARY_CHARSET
) : MFRemoteFileAttributes<ConnectionConfig, UssRequester>, Copyable {

/**
Expand All @@ -102,7 +103,8 @@ data class RemoteUssAttributes(
gid = ussFile.gid,
groupId = ussFile.groupId,
modificationTime = ussFile.modificationTime,
symlinkTarget = ussFile.target
symlinkTarget = ussFile.target,
charset = DEFAULT_BINARY_CHARSET
)

/**
Expand Down Expand Up @@ -176,8 +178,6 @@ data class RemoteUssAttributes(
|| mode == FileModeValue.READ_WRITE_EXECUTE.mode
}

var charset: Charset = DEFAULT_BINARY_CHARSET

override var contentMode: XIBMDataType = XIBMDataType(XIBMDataType.Type.BINARY)

override val isCopyPossible: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ class RemoteUssAttributesService(
gid = newAttributes.gid,
groupId = newAttributes.groupId,
modificationTime = newAttributes.modificationTime,
symlinkTarget = newAttributes.symlinkTarget
symlinkTarget = newAttributes.symlinkTarget,
charset = oldAttributes.charset
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package org.zowe.explorer.dataops.fetch

import com.intellij.ide.util.treeView.AbstractTreeNode
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.vfs.VirtualFile
Expand All @@ -24,6 +23,7 @@ import org.zowe.explorer.config.connect.ConnectionConfigBase
import org.zowe.explorer.dataops.DataOpsManager
import org.zowe.explorer.dataops.Query
import org.zowe.explorer.dataops.RemoteQuery
import org.zowe.explorer.dataops.UnitRemoteQueryImpl
import org.zowe.explorer.dataops.exceptions.CallException
import org.zowe.explorer.dataops.services.ErrorSeparatorService
import org.zowe.explorer.utils.castOrNull
Expand Down Expand Up @@ -223,7 +223,8 @@ abstract class RemoteFileFetchProviderBase<Connection : ConnectionConfigBase, Re
}
}

refreshCacheOfCollidingQuery(query, files)
//TODO: the only known evidence to use refresh of colliding query is related to BatchedQuery
query.castOrNull(UnitRemoteQueryImpl::class.java) ?: refreshCacheOfCollidingQuery(query, files)

cache[query] = files
cacheState[query] = CacheState.FETCHED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ abstract class AbstractMFLoggerBase<PInfo: MFProcessInfo, LFetcher: LogFetcher<P
this.onFinishHandler = finishHandler
}

/**
* Fetch log files
*/
override fun fetchLog() {
logFetcher.fetchLog(mfProcessInfo)
}

/**
* Sets onNextLog handler.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/org/zowe/explorer/dataops/log/MFLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ interface MFLogger<LFetcher: LogFetcher<out MFProcessInfo>> {
*/
fun onLogFinished(finishHandler: () -> Unit)

/**
* Fetch log files
*/
fun fetchLog()

/**
* Sets handler for event after requesting next portion of mainframe log.
* @param nextLogHandler handler that will be invoked after next request to fetching log from mainframe.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ class InfoOperationRunner : OperationRunner<InfoOperation, SystemsResponse> {
.cancelByIndicator(progressIndicator)
.execute()
if (!response.isSuccessful) {
val headMessage = responseMessageMap[response.message()] ?: response.message()
log.info("Test connection response: $response")
val zosmfMessage = response.message().trim()
val headMessage = if (zosmfMessage.isNotEmpty())
responseMessageMap[zosmfMessage] ?: zosmfMessage
else responseMessageMap["Unauthorized"] ?: zosmfMessage
throw CallException(response, headMessage)
}
return response.body() ?: throw CallException(response, "Cannot parse z/OSMF info request body")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class CrossSystemMemberOrUssFileToPdsMoverFactory : OperationRunnerFactory {
*/
class CrossSystemMemberOrUssFileToPdsMover(val dataOpsManager: DataOpsManager) : AbstractFileMover() {

private val sourceContentType = "text/plain; charset=UTF-8"

/**
* Checks that source is member or uss file, dest is partitioned data set, and they are located inside different systems.
* @see OperationRunner.canRun
Expand Down Expand Up @@ -94,17 +96,22 @@ class CrossSystemMemberOrUssFileToPdsMover(val dataOpsManager: DataOpsManager) :
else XIBMDataType(XIBMDataType.Type.TEXT)

val sourceContent = sourceFile.contentsToByteArray()
val contentToUpload =
if (sourceFile.fileType.isBinary) sourceContent
else sourceContent.toString(sourceFile.charset).replace("\r\n", "\n")
.toByteArray(DEFAULT_TEXT_CHARSET).addNewLine()

// do not convert bytes to default ISO. z/OSMF does this conversion by calling iconv and produce the desired result
val contentToUpload = if (sourceFile.fileType.isBinary) sourceContent else
sourceContent
.toString(sourceFile.charset)
.replace("\r\n", "\n")
.toByteArray()
.addNewLine()

val response = apiWithBytesConverter<DataAPI>(destConnectionConfig).writeToDatasetMember(
authorizationToken = destConnectionConfig.authToken,
datasetName = destAttributes.name,
memberName = memberName,
content = contentToUpload,
xIBMDataType = xIBMDataType
xIBMDataType = xIBMDataType,
contentType = sourceContentType
).applyIfNotNull(progressIndicator) { indicator ->
cancelByIndicator(indicator)
}.execute()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class CrossSystemUssDirMover(val dataOpsManager: DataOpsManager) : AbstractFileM
sourceFileFetchProvider.reload(sourceQuery)
}

val pathToDir = destAttributes.path + "/" + sourceFile.name
val pathToDir = destAttributes.path + "/" + (operation.newName ?: sourceFile.name)

if (operation.forceOverwriting) {
destFile.children.firstOrNull { it.name == sourceFile.name }?.let {
Expand All @@ -114,7 +114,7 @@ class CrossSystemUssDirMover(val dataOpsManager: DataOpsManager) : AbstractFileM
val createdDirFile = attributesService.getOrCreateVirtualFile(
RemoteUssAttributes(
destAttributes.path,
UssFile(sourceFile.name, "drwxrwxrwx"),
UssFile(operation.newName ?: sourceFile.name, "drwxrwxrwx"),
destConnectionConfig.url,
destConnectionConfig
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,14 @@ class PurgeJobAction : AnAction() {
jobsByFilterWaitingPurgeMap.keys.forEach { filterNode ->
val query = filterNode.query
if (query != null) {
// If we performed purge from the job filter defined with JOBID, then OWNER and PREFIX should be "*".
// Do not send userCorrelator for a job filter defined with JOBID as described in the specification
val response = api<JESApi>(query.connectionConfig).getFilteredJobs(
basicCredentials = query.connectionConfig.authToken,
owner = query.request.owner,
prefix = query.request.prefix,
userCorrelator = query.request.userCorrelatorFilter,
owner = query.request.owner.ifEmpty { "*" },
prefix = query.request.prefix.ifEmpty { "*" },
jobId = query.request.jobId.ifEmpty { null },
userCorrelator = query.request.userCorrelatorFilter.ifEmpty { null },
execData = ExecData.YES
).execute()
val result = response.body()
Expand Down Expand Up @@ -153,9 +156,9 @@ class PurgeJobAction : AnAction() {
if (filtersWithRefreshErrors.isNotEmpty()) {
var errorFilterNames = ""
for (entry in filtersWithRefreshErrors) {
errorFilterNames += entry.key.unit.name + "\n"
errorFilterNames += entry.key.name + "\n"
}
throw RuntimeException("Refresh error. Failed filters are: $errorFilterNames")
throw RuntimeException("Refresh error. Failed job filters are: $errorFilterNames")
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/zowe/explorer/explorer/ui/DSMaskNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ class DSMaskNode(
return {
val datasetAttributes = when (it) {
is FileLikeDatasetNode -> DataOpsManager.getService()
.tryToGetAttributes(it.virtualFile) as RemoteDatasetAttributes
.tryToGetAttributes(it.virtualFile) as? RemoteDatasetAttributes

is LibraryNode -> DataOpsManager.getService().tryToGetAttributes(it.virtualFile) as RemoteDatasetAttributes
is LibraryNode -> DataOpsManager.getService().tryToGetAttributes(it.virtualFile) as? RemoteDatasetAttributes
else -> null
}
when (key) {
Expand Down
Loading

0 comments on commit 0aff128

Please sign in to comment.