Skip to content
GitLab
Explore
Projects
Groups
Snippets
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
CoTA
cota-backend
Commits
c47948b0
Commit
c47948b0
authored
4 months ago
by
mamunozgil
Browse files
Options
Download
Email Patches
Plain Diff
Added volumes for the backend host
parent
f8255ef9
No related merge requests found
Pipeline
#10761
passed with stage
in 18 seconds
Changes
2
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/main/java/de/hftstuttgart/dtabackend/utils/DockerUtil.java
+64
-1
...ain/java/de/hftstuttgart/dtabackend/utils/DockerUtil.java
src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+150
-232
...ava/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
with
214 additions
and
233 deletions
+214
-233
src/main/java/de/hftstuttgart/dtabackend/utils/DockerUtil.java
+
64
-
1
View file @
c47948b0
...
@@ -5,6 +5,9 @@ import com.github.dockerjava.api.command.CreateContainerResponse;
...
@@ -5,6 +5,9 @@ import com.github.dockerjava.api.command.CreateContainerResponse;
import
com.github.dockerjava.api.exception.DockerException
;
import
com.github.dockerjava.api.exception.DockerException
;
import
com.github.dockerjava.api.model.Bind
;
import
com.github.dockerjava.api.model.Bind
;
import
com.github.dockerjava.api.model.HostConfig
;
import
com.github.dockerjava.api.model.HostConfig
;
import
com.github.dockerjava.api.model.Mount
;
import
com.github.dockerjava.api.model.MountType
;
import
com.github.dockerjava.api.model.Volume
;
import
com.github.dockerjava.core.DefaultDockerClientConfig
;
import
com.github.dockerjava.core.DefaultDockerClientConfig
;
import
com.github.dockerjava.core.DockerClientConfig
;
import
com.github.dockerjava.core.DockerClientConfig
;
import
com.github.dockerjava.core.DockerClientImpl
;
import
com.github.dockerjava.core.DockerClientImpl
;
...
@@ -15,6 +18,8 @@ import org.springframework.core.env.Environment;
...
@@ -15,6 +18,8 @@ import org.springframework.core.env.Environment;
import
org.springframework.stereotype.Component
;
import
org.springframework.stereotype.Component
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.stream.Collectors
;
@Component
@Component
public
class
DockerUtil
{
public
class
DockerUtil
{
...
@@ -37,7 +42,7 @@ public class DockerUtil {
...
@@ -37,7 +42,7 @@ public class DockerUtil {
dockerClient
=
DockerClientImpl
.
getInstance
(
dockerClientConfig
,
httpClient
);
dockerClient
=
DockerClientImpl
.
getInstance
(
dockerClientConfig
,
httpClient
);
}
}
public
int
runContainer
(
String
image
,
Bind
...
binds
)
throws
InterruptedException
,
IOException
public
int
runContainer
WithBinds
(
String
image
,
Bind
...
binds
)
throws
InterruptedException
,
IOException
{
{
LOG
.
debug
(
String
.
format
(
"pull image: %s"
,
image
));
LOG
.
debug
(
String
.
format
(
"pull image: %s"
,
image
));
try
{
try
{
...
@@ -87,4 +92,62 @@ public class DockerUtil {
...
@@ -87,4 +92,62 @@ public class DockerUtil {
return
ret
;
return
ret
;
}
}
public
int
runContainerWithVolumes
(
String
image
,
Volume
...
volumes
)
throws
InterruptedException
,
IOException
{
LOG
.
debug
(
String
.
format
(
"Pulling image: %s"
,
image
));
try
{
dockerClient
.
pullImageCmd
(
image
)
.
start
()
.
awaitCompletion
()
.
close
();
}
catch
(
DockerException
e
)
{
LOG
.
error
(
String
.
format
(
"Pulling Docker image %s failed with %s, trying with local image"
,
image
,
e
.
getMessage
()));
}
LOG
.
debug
(
"Creating container"
);
CreateContainerResponse
containerResponse
;
try
{
// Prepare the host configuration with volumes
HostConfig
hostConfig
=
HostConfig
.
newHostConfig
()
.
withMounts
(
Arrays
.
stream
(
volumes
)
.
map
(
volume
->
new
Mount
().
withTarget
(
volume
.
getPath
()).
withType
(
MountType
.
VOLUME
))
.
collect
(
Collectors
.
toList
())
);
// Create the container with the configured volumes
containerResponse
=
dockerClient
.
createContainerCmd
(
"testcontainer"
)
.
withImage
(
image
)
.
withHostConfig
(
hostConfig
)
.
exec
();
}
catch
(
DockerException
e
)
{
LOG
.
error
(
String
.
format
(
"Creating Docker Testrunner container failed with %s"
,
e
.
getMessage
()));
throw
e
;
}
LOG
.
debug
(
String
.
format
(
"Container created: %s"
,
containerResponse
.
getId
()));
LOG
.
debug
(
String
.
format
(
"Starting container %s"
,
containerResponse
.
getId
()));
dockerClient
.
startContainerCmd
(
containerResponse
.
getId
()).
exec
();
LOG
.
debug
(
String
.
format
(
"Waiting for completion of container %s"
,
containerResponse
.
getId
()));
int
ret
=
dockerClient
.
waitContainerCmd
(
containerResponse
.
getId
())
.
start
()
.
awaitCompletion
()
.
awaitStatusCode
();
LOG
.
debug
(
String
.
format
(
"Container completed with status %d"
,
ret
));
LOG
.
debug
(
String
.
format
(
"Deleting container %s"
,
containerResponse
.
getId
()));
dockerClient
.
removeContainerCmd
(
containerResponse
.
getId
())
.
withRemoveVolumes
(
true
)
.
exec
();
return
ret
;
}
}
}
This diff is collapsed.
Click to expand it.
src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+
150
-
232
View file @
c47948b0
...
@@ -3,7 +3,6 @@ package de.hftstuttgart.dtabackend.utils;
...
@@ -3,7 +3,6 @@ package de.hftstuttgart.dtabackend.utils;
import
com.fasterxml.jackson.core.exc.StreamReadException
;
import
com.fasterxml.jackson.core.exc.StreamReadException
;
import
com.fasterxml.jackson.databind.DatabindException
;
import
com.fasterxml.jackson.databind.DatabindException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.github.dockerjava.api.model.Bind
;
import
com.github.dockerjava.api.model.Volume
;
import
com.github.dockerjava.api.model.Volume
;
import
de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile
;
import
de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile
;
...
@@ -31,12 +30,11 @@ import java.util.regex.Pattern;
...
@@ -31,12 +30,11 @@ import java.util.regex.Pattern;
import
java.util.stream.Collectors
;
import
java.util.stream.Collectors
;
@Component
@Component
public
class
ExecuteTestUtil
{
public
class
ExecuteTestUtil
{
private
static
final
Logger
LOG
=
LogManager
.
getLogger
(
ExecuteTestUtil
.
class
);
private
static
final
Logger
LOG
=
LogManager
.
getLogger
(
ExecuteTestUtil
.
class
);
private
final
DockerUtil
dockerUtil
;
private
final
DockerUtil
dockerUtil
;
private
final
String
assignmentBasePath
;
private
final
String
assignmentBasePath
;
private
final
Path
testTmpPathHost
;
public
ExecuteTestUtil
(
public
ExecuteTestUtil
(
Environment
env
,
Environment
env
,
...
@@ -49,281 +47,201 @@ public class ExecuteTestUtil {
...
@@ -49,281 +47,201 @@ public class ExecuteTestUtil {
env
.
getProperty
(
"data.dir"
),
env
.
getProperty
(
"data.dir"
),
env
.
getProperty
(
"data.dir.test.folder.name"
));
env
.
getProperty
(
"data.dir.test.folder.name"
));
this
.
assignmentBasePath
=
p
.
toAbsolutePath
().
toString
();
this
.
assignmentBasePath
=
p
.
toAbsolutePath
().
toString
();
// set path of temporary directory on host _and_ inside our container, _must_ be identical
this
.
testTmpPathHost
=
Paths
.
get
(
env
.
getProperty
(
"host.tests.tmp.dir"
));
}
}
public
ResultSummary
runTests
(
String
assignmentId
,
Path
workDirectory
)
throws
IOException
,
InterruptedException
{
public
ResultSummary
runTests
(
String
assignmentId
,
Path
workDirectory
)
throws
IOException
,
InterruptedException
{
//
d
efine paths for the test, the submission and where the result is
to be
expected afterwards
//
D
efine paths for the test, the submission
,
and where the result is expected afterwards
Path
testPath
=
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"/test"
);
Path
testPath
=
Paths
.
get
(
"/data/test"
);
// Volume mount path in the container
Path
srcPath
=
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"/src"
);
Path
srcPath
=
Paths
.
get
(
"/data/src"
);
// Volume mount path in the container
Path
resultPath
=
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"/result"
);
Path
resultPath
=
Paths
.
get
(
"/data/result"
);
// Volume mount path in the container
//
c
lone stored test to t
mpdir
//
C
lone stored test to t
estPath
LOG
.
debug
(
"
c
opying pre-downloaded unit
t
test repo"
);
LOG
.
debug
(
"
C
opying pre-downloaded unit
test repo"
);
FileUtil
.
copyFolder
(
FileUtil
.
copyFolder
(
Paths
.
get
(
assignmentBasePath
,
assignmentId
),
Paths
.
get
(
assignmentBasePath
,
assignmentId
),
testPath
);
testPath
);
LOG
.
debug
(
"copying exercise manifest from: %s in testPath: %s"
,
LOG
.
debug
(
"Copying exercise manifest"
);
assignmentBasePath
+
assignmentId
+
"_checkout"
,
Files
.
copy
(
Paths
.
get
(
testPath
.
toString
()
);
Files
.
copy
(
Paths
.
get
(
assignmentBasePath
,
assignmentId
+
"_checkout"
,
assignmentBasePath
,
assignmentId
+
"_checkout"
,
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
),
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
),
Paths
.
get
(
Paths
.
get
(
testPath
.
toString
(),
testPath
.
toString
(),
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
));
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
));
LOG
.
debug
(
"
c
opy test config"
);
LOG
.
debug
(
"
C
opy
ing
test config"
);
Files
.
copy
(
Files
.
copy
(
Paths
.
get
(
assignmentBasePath
,
assignmentId
+
".txt"
),
Paths
.
get
(
assignmentBasePath
,
assignmentId
+
".txt"
),
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"config.txt"
));
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"config.txt"
));
Files
.
createDirectory
(
resultPath
);
Files
.
createDirectory
(
resultPath
);
LOG
.
info
(
"
r
eading test config"
);
LOG
.
info
(
"
R
eading test config"
);
Matcher
config
=
RegexUtil
.
extractConfig
(
Matcher
config
=
RegexUtil
.
extractConfig
(
new
FileInputStream
(
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"config.txt"
).
toFile
()),
Pattern
.
compile
(
RegexUtil
.
DTA_TESTCONFIGREGEX
));
new
FileInputStream
(
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"config.txt"
).
toFile
()),
Pattern
.
compile
(
RegexUtil
.
DTA_TESTCONFIGREGEX
));
String
image
=
""
;
String
image
=
""
;
if
(
config
==
null
)
if
(
config
==
null
)
{
{
config
=
RegexUtil
.
extractConfig
(
config
=
RegexUtil
.
extractConfig
(
new
FileInputStream
(
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"config.txt"
).
toFile
()),
Pattern
.
compile
(
RegexUtil
.
TESTCONFIGREGEX
));
new
FileInputStream
(
Paths
.
get
(
workDirectory
.
toAbsolutePath
().
toString
(),
"config.txt"
).
toFile
()),
Pattern
.
compile
(
RegexUtil
.
TESTCONFIGREGEX
));
if
(
config
==
null
)
{
if
(
config
==
null
)
throw
new
RuntimeException
(
"Couldn't find repo config for unit test image extraction"
);
{
}
throw
new
RuntimeException
(
"couldn't find repo config for unittest image extraction"
);
image
=
config
.
group
(
4
);
}
}
else
{
image
=
config
.
group
(
4
);
image
=
config
.
group
(
5
);
}
else
{
image
=
config
.
group
(
5
);
}
}
// define the paths to mount as Binds from Host to the test-container
Path
testPathHost
=
Paths
.
get
(
testTmpPathHost
.
toAbsolutePath
().
toString
(),
workDirectory
.
getName
(
workDirectory
.
getNameCount
()-
1
).
toString
(),
testPath
.
getName
(
testPath
.
getNameCount
()-
1
).
toString
()
);
Path
srcPathHost
=
Paths
.
get
(
testTmpPathHost
.
toAbsolutePath
().
toString
(),
workDirectory
.
getName
(
workDirectory
.
getNameCount
()-
1
).
toString
(),
srcPath
.
getName
(
srcPath
.
getNameCount
()-
1
).
toString
()
);
Path
resultPathHost
=
Paths
.
get
(
testTmpPathHost
.
toAbsolutePath
().
toString
(),
workDirectory
.
getName
(
workDirectory
.
getNameCount
()-
1
).
toString
(),
resultPath
.
getName
(
resultPath
.
getNameCount
()-
1
).
toString
()
);
//
s
tart test-container with professor
given image and
bind
mounts for test, submission and result
//
S
tart
the
test-container with professor
-
given image and
volume
mounts for test, submission
,
and result
dockerUtil
.
runContainer
(
dockerUtil
.
runContainer
(
image
,
image
,
new
Bind
(
testPathHost
.
toAbsolutePath
().
toString
(),
new
Volume
(
"/data/test"
)
)
,
new
Volume
(
"/data/test"
),
new
Bind
(
srcPathHost
.
toAbsolutePath
().
toString
(),
new
Volume
(
"/data/src"
)
)
,
new
Volume
(
"/data/src"
),
new
Bind
(
resultPathHost
.
toAbsolutePath
().
toString
(),
new
Volume
(
"/data/result"
)
)
new
Volume
(
"/data/result"
)
);
);
ResultSummary
resultSummary
=
generateResult
(
assignmentId
,
resultPath
,
testPath
Host
);
ResultSummary
resultSummary
=
generateResult
(
assignmentId
,
resultPath
,
testPath
);
return
resultSummary
;
return
resultSummary
;
}
}
private
ResultSummary
generateResult
(
String
assignmentId
,
Path
resultPath
,
Path
testPath
Host
)
private
ResultSummary
generateResult
(
String
assignmentId
,
Path
resultPath
,
Path
testPath
)
throws
IOException
,
StreamReadException
,
DatabindException
,
MalformedURLException
{
throws
IOException
,
StreamReadException
,
DatabindException
,
MalformedURLException
{
//
d
efine expected result file
//
D
efine expected result file
File
resultFile
=
Paths
.
get
(
resultPath
.
toAbsolutePath
().
toString
(),
"result.json"
).
toFile
();
File
resultFile
=
Paths
.
get
(
resultPath
.
toAbsolutePath
().
toString
(),
"result.json"
).
toFile
();
//
c
heck if result file is there
//
C
heck if result file is there
if
(!
resultFile
.
exists
()
||
!
resultFile
.
isFile
())
{
if
(!
resultFile
.
exists
()
||
!
resultFile
.
isFile
())
{
LOG
.
error
(
String
.
format
(
"Could not find result file in %s"
,
resultFile
.
getAbsolutePath
()));
LOG
.
error
(
String
.
format
(
"Could not find result file in %s"
,
resultFile
.
getAbsolutePath
()));
throw
new
RuntimeException
(
"
n
o resultfile found"
);
throw
new
RuntimeException
(
"
N
o result
file found"
);
}
}
LOG
.
debug
(
"
p
arse results
json
"
);
LOG
.
debug
(
"
P
arse results
JSON
"
);
ObjectMapper
objectMapper
=
new
ObjectMapper
();
ObjectMapper
objectMapper
=
new
ObjectMapper
();
ResultSummary
resultSummary
=
objectMapper
.
readValue
(
resultFile
.
toURI
().
toURL
(),
ResultSummary
.
class
);
ResultSummary
resultSummary
=
objectMapper
.
readValue
(
resultFile
.
toURI
().
toURL
(),
ResultSummary
.
class
);
LOG
.
debug
(
"
r
esult
json
returned time "
+
resultSummary
.
timestamp
+
" with "
+
resultSummary
.
results
.
size
()+
" test results."
);
LOG
.
debug
(
"
R
esult
JSON
returned time "
+
resultSummary
.
timestamp
+
" with "
+
resultSummary
.
results
.
size
()
+
" test results."
);
LOG
.
info
(
"Checking for optional test competency profile information for p
a
edagogical agent functionality..."
);
LOG
.
info
(
"Checking for optional test competency profile information for pedagogical agent functionality..."
);
List
<
TestCompetencyProfile
>
testCompetencyProfiles
=
CompetencyAssessmentUtil
.
readTestCompetencyProfiles
(
testPath
Host
,
CompetencyAssessmentUtil
.
TEST_COMPETENCY_MANIFEST_FILE_NAME
);
List
<
TestCompetencyProfile
>
testCompetencyProfiles
=
CompetencyAssessmentUtil
.
readTestCompetencyProfiles
(
testPath
,
CompetencyAssessmentUtil
.
TEST_COMPETENCY_MANIFEST_FILE_NAME
);
LOG
.
debug
(
String
.
format
(
LOG
.
debug
(
String
.
format
(
"Reading Test Competency Profiles: basePath=%s, fileName=%s"
,
"Reading Test Competency Profiles: basePath=%s, fileName=%s"
,
testPathHost
,
testPath
,
CompetencyAssessmentUtil
.
TEST_COMPETENCY_MANIFEST_FILE_NAME
CompetencyAssessmentUtil
.
TEST_COMPETENCY_MANIFEST_FILE_NAME
));
));
if
(
testCompetencyProfiles
!=
null
)
{
if
(
testCompetencyProfiles
!=
null
)
{
LOG
.
info
(
"Found optional test competency profiles, generating agent profile data..."
);
LOG
.
info
(
"Found optional test competency profiles, generating agent profile data..."
);
resultSummary
.
overallTestCompetencyProfile
=
CompetencyAssessmentUtil
.
packFloats
(
CompetencyAssessmentUtil
.
sumTestCompetencyProfiles
(
testCompetencyProfiles
));
resultSummary
.
overallTestCompetencyProfile
=
CompetencyAssessmentUtil
.
packFloats
(
CompetencyAssessmentUtil
.
sumTestCompetencyProfiles
(
testCompetencyProfiles
));
resultSummary
.
successfulTestCompetencyProfile
=
CompetencyAssessmentUtil
.
packFloats
(
CompetencyAssessmentUtil
.
sumSuccessfulCompetencyProfiles
(
testCompetencyProfiles
,
resultSummary
,
true
));
resultSummary
.
successfulTestCompetencyProfile
=
CompetencyAssessmentUtil
.
packFloats
(
CompetencyAssessmentUtil
.
sumSuccessfulCompetencyProfiles
(
testCompetencyProfiles
,
resultSummary
,
true
));
LOG
.
info
(
"Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality..."
);
LOG
.
info
(
"Checking for optional exercise competency profile information for pedagogical agent exercise recommendation functionality..."
);
//testPathHost or assignmentBasePath
Path
exerciseManifestFile
=
Paths
.
get
(
testPath
.
toString
(),
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
);
Path
exerciseManifestFile
=
Paths
.
get
(
testPathHost
.
toString
(),
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
);
LOG
.
debug
(
String
.
format
(
LOG
.
debug
(
String
.
format
(
"Constructing Path for exercise manifest: testPath=%s, fileName=%s -> Resulting Path=%s"
,
"Constructing Path for exercise manifest: testPath=%s, fileName=%s -> Resulting Path=%s"
,
testPath
.
toString
(),
testPathHost
.
toString
(),
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
,
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
,
exerciseManifestFile
.
toString
()
exerciseManifestFile
.
toString
()
));
));
if
(
Files
.
exists
(
exerciseManifestFile
))
{
if
(
Files
.
exists
(
exerciseManifestFile
))
{
LOG
.
info
(
"Found optional exercise competency profiles, generating recommendations..."
);
LOG
.
info
(
"Found optional exercise competency profiles, generating recommendations..."
);
resultSummary
.
recommendations
=
recommendNextExercises
(
assignmentId
,
testPath
,
testCompetencyProfiles
,
resultSummary
);
resultSummary
.
recommendations
=
recommendNextExercises
(
assignmentId
,
testPathHost
,
testCompetencyProfiles
,
resultSummary
);
}
}
}
}
return
resultSummary
;
return
resultSummary
;
}
}
/*
/*
* exercise recommendation part
* exercise recommendation part
*/
*/
public
List
<
Recommendation
>
recommendNextExercises
(
String
assignmentId
,
Path
testPathHost
,
List
<
TestCompetencyProfile
>
testCompetencyProfiles
,
ResultSummary
resultSummary
)
public
List
<
Recommendation
>
recommendNextExercises
(
String
assignmentId
,
Path
testPathHost
,
List
<
TestCompetencyProfile
>
testCompetencyProfiles
,
ResultSummary
resultSummary
)
throws
FileNotFoundException
{
throws
FileNotFoundException
{
LOG
.
debug
(
"Starting recommendNextExercises with assignmentId: {}"
,
assignmentId
);
// fetch repo url from original test upload
Pattern
pattern
=
Pattern
.
compile
(
RegexUtil
.
DTA_TESTCONFIGREGEX
);
// fetch repo url from original test upload
File
file
=
Paths
.
get
(
assignmentBasePath
,
assignmentId
+
".txt"
).
toFile
();
Pattern
pattern
=
Pattern
.
compile
(
RegexUtil
.
DTA_TESTCONFIGREGEX
);
FileInputStream
configFileStream
=
new
FileInputStream
(
file
);
LOG
.
debug
(
"Compiled regex pattern for DTA_TESTCONFIGREGEX."
);
Matcher
config
=
RegexUtil
.
extractConfig
(
configFileStream
,
pattern
);
String
testRepoURL
=
config
.
group
(
1
);
File
file
=
Paths
.
get
(
assignmentBasePath
,
assignmentId
+
".txt"
).
toFile
();
LOG
.
debug
(
"Resolved file path for assignmentId {}: {}"
,
assignmentId
,
file
.
getAbsolutePath
());
List
<
ExerciseCompetencyProfile
>
exerciseCompetencyProfiles
=
CompetencyAssessmentUtil
.
readExerciseCompetencyProfiles
(
testPathHost
,
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
);
FileInputStream
configFileStream
=
new
FileInputStream
(
file
);
int
currentTopicIndex
=
0
;
LOG
.
debug
(
"Opened FileInputStream for file: {}"
,
file
.
getAbsolutePath
());
float
currentDifficulty
=
0.0f
;
Matcher
config
=
RegexUtil
.
extractConfig
(
configFileStream
,
pattern
);
//build course topic order
LOG
.
debug
(
"Extracted configuration using regex pattern."
);
Map
<
String
,
Integer
>
topicOrder
=
new
HashMap
<>();
int
order
=
1
;
String
testRepoURL
=
config
.
group
(
1
)
+
config
.
group
(
4
);
for
(
ExerciseCompetencyProfile
e
:
exerciseCompetencyProfiles
)
{
LOG
.
debug
(
"Constructed testRepoURL: {}"
,
testRepoURL
);
if
(!
topicOrder
.
containsKey
(
e
.
exerciseTopicName
))
{
topicOrder
.
put
(
e
.
exerciseTopicName
,
order
++);
List
<
ExerciseCompetencyProfile
>
exerciseCompetencyProfiles
=
CompetencyAssessmentUtil
.
readExerciseCompetencyProfiles
(
}
testPathHost
,
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
);
if
(
e
.
exerciseURL
.
equals
(
testRepoURL
))
{
LOG
.
debug
(
"Read exercise competency profiles from path: {}"
,
testPathHost
.
resolve
(
CompetencyAssessmentUtil
.
EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
));
currentTopicIndex
=
order
;
currentDifficulty
=
e
.
difficulty
;
int
currentTopicIndex
=
0
;
}
float
currentDifficulty
=
0.0f
;
// build course topic order
LOG
.
debug
(
"Building course topic order."
);
Map
<
String
,
Integer
>
topicOrder
=
new
HashMap
<>();
int
order
=
1
;
for
(
ExerciseCompetencyProfile
e
:
exerciseCompetencyProfiles
)
{
if
(!
topicOrder
.
containsKey
(
e
.
exerciseTopicName
))
{
topicOrder
.
put
(
e
.
exerciseTopicName
,
order
++);
LOG
.
debug
(
"Added topic {} to topicOrder with order {}"
,
e
.
exerciseTopicName
,
order
-
1
);
}
}
if
(
e
.
exerciseURL
.
equals
(
testRepoURL
))
{
currentTopicIndex
=
order
;
//filter exercises according to success
currentDifficulty
=
e
.
difficulty
;
float
[]
unsuccessful
=
CompetencyAssessmentUtil
.
sumSuccessfulCompetencyProfiles
(
testCompetencyProfiles
,
resultSummary
,
false
);
LOG
.
debug
(
"Matched current testRepoURL to topic: {}, index: {}, difficulty: {}"
,
e
.
exerciseTopicName
,
currentTopicIndex
,
currentDifficulty
);
List
<
ExerciseCompetencyProfile
>
filteredExercises
=
filterExercisesByTopicsAndDifficulty
(
exerciseCompetencyProfiles
,
topicOrder
,
currentTopicIndex
,
testRepoURL
,
currentDifficulty
,
unsuccessful
,
resultSummary
);
//compute recommendations
List
<
Recommendation
>
recommendedExercises
=
new
ArrayList
<>();
for
(
ExerciseCompetencyProfile
exerciseProfile
:
filteredExercises
)
{
Recommendation
recommendation
=
new
Recommendation
(
exerciseProfile
.
exerciseTopicName
,
exerciseProfile
.
exerciseURL
,
exerciseProfile
.
exerciseName
,
exerciseProfile
.
difficulty
,
calculateScore
(
exerciseProfile
,
unsuccessful
,
topicOrder
,
currentDifficulty
));
recommendedExercises
.
add
(
recommendation
);
LOG
.
info
(
"Recommending exercise "
+
recommendation
.
topic
+
"/"
+
recommendation
.
exerciseName
+
" with score "
+
recommendation
.
score
);
}
}
//sort the recommendations for successful or resilient learners, otherwise reverse in display
recommendedExercises
.
stream
().
sorted
(
Recommendation
.
COMPARE_BY_SCORE
).
collect
(
Collectors
.
toList
());
return
recommendedExercises
;
}
}
// filter exercises according to success
public
static
List
<
ExerciseCompetencyProfile
>
filterExercisesByTopicsAndDifficulty
(
List
<
ExerciseCompetencyProfile
>
exerciseCompetencyProfiles
,
LOG
.
debug
(
"Filtering exercises according to success."
);
Map
<
String
,
Integer
>
topicOrder
,
int
currentTopicIndex
,
String
testRepoURL
,
float
currentDifficulty
,
float
[]
unsuccessful
,
float
[]
unsuccessful
=
CompetencyAssessmentUtil
.
sumSuccessfulCompetencyProfiles
(
testCompetencyProfiles
,
resultSummary
,
false
);
ResultSummary
resultSummary
)
{
LOG
.
debug
(
"Computed unsuccessful competency profile: {}"
,
unsuccessful
);
//filter out all advanced topics in any case
//option for later: include next topic if fullsuccess and current difficulty == max difficulty
List
<
ExerciseCompetencyProfile
>
filteredExercises
=
filterExercisesByTopicsAndDifficulty
(
List
<
ExerciseCompetencyProfile
>
filteredExercises
=
exerciseCompetencyProfiles
.
stream
()
exerciseCompetencyProfiles
,
topicOrder
,
currentTopicIndex
,
testRepoURL
,
.
filter
(
testProfile
->
topicOrder
.
get
(
testProfile
.
exerciseTopicName
)
<=
currentTopicIndex
)
currentDifficulty
,
unsuccessful
,
resultSummary
);
.
collect
(
Collectors
.
toList
());
LOG
.
debug
(
"Filtered exercises count: {}"
,
filteredExercises
.
size
());
//filter by difficulty according to success
if
(
isFullSuccess
(
unsuccessful
))
{
filteredExercises
=
filteredExercises
.
stream
().
filter
(
profile
->
profile
.
difficulty
>=
currentDifficulty
&&
!
testRepoURL
.
equals
(
profile
.
exerciseURL
)).
collect
(
Collectors
.
toList
());
}
else
{
filteredExercises
=
filteredExercises
.
stream
().
filter
(
profile
->
profile
.
difficulty
<=
currentDifficulty
).
collect
(
Collectors
.
toList
());
}
// compute recommendations
return
filteredExercises
;
LOG
.
debug
(
"Computing recommendations from filtered exercises."
);
}
List
<
Recommendation
>
recommendedExercises
=
new
ArrayList
<>();
for
(
ExerciseCompetencyProfile
exerciseProfile
:
filteredExercises
)
{
public
static
boolean
isFullSuccess
(
float
[]
unsuccessful
)
{
Recommendation
recommendation
=
new
Recommendation
(
for
(
float
value
:
unsuccessful
)
{
exerciseProfile
.
exerciseTopicName
,
exerciseProfile
.
exerciseURL
,
exerciseProfile
.
exerciseName
,
if
(
value
!=
0.0f
)
{
exerciseProfile
.
difficulty
,
calculateScore
(
exerciseProfile
,
unsuccessful
,
topicOrder
,
currentDifficulty
));
return
false
;
recommendedExercises
.
add
(
recommendation
);
}
LOG
.
info
(
"Recommending exercise {}/{} with score {}"
,
recommendation
.
topic
,
recommendation
.
exerciseName
,
recommendation
.
score
);
}
return
true
;
}
}
// sort the recommendations for successful or resilient learners, otherwise reverse in display
public
static
float
calculateScore
(
ExerciseCompetencyProfile
exerciseProfile
,
float
[]
unsuccessful
,
Map
<
String
,
Integer
>
topicOrder
,
float
currentDifficulty
)
{
LOG
.
debug
(
"Sorting recommendations."
);
//ensure factor 1 for full success not to blank out score, thus offset the base
recommendedExercises
.
stream
().
sorted
(
Recommendation
.
COMPARE_BY_SCORE
).
collect
(
Collectors
.
toList
());
float
score
=
1.0f
;
//competency profile difference to not fully achieved competencies component
LOG
.
debug
(
"Completed recommendNextExercises with {} recommendations."
,
recommendedExercises
.
size
());
for
(
int
i
=
0
;
i
<
exerciseProfile
.
competencyAssessments
.
length
-
1
;
i
++)
{
return
recommendedExercises
;
score
+=
exerciseProfile
.
competencyAssessments
[
i
]
*
unsuccessful
[
i
];
}
}
public
static
List
<
ExerciseCompetencyProfile
>
filterExercisesByTopicsAndDifficulty
(
//difficulty component
List
<
ExerciseCompetencyProfile
>
exerciseCompetencyProfiles
,
score
=
score
*
(
exerciseProfile
.
difficulty
*(
0.5f
+
Math
.
abs
(
currentDifficulty
-
exerciseProfile
.
difficulty
)));
Map
<
String
,
Integer
>
topicOrder
,
int
currentTopicIndex
,
String
testRepoURL
,
float
currentDifficulty
,
float
[]
unsuccessful
,
ResultSummary
resultSummary
)
{
//topic component
score
*=
topicOrder
.
get
(
exerciseProfile
.
exerciseTopicName
);
LOG
.
debug
(
"Starting filterExercisesByTopicsAndDifficulty with currentTopicIndex: {}, currentDifficulty: {}, testRepoURL: {}"
,
currentTopicIndex
,
currentDifficulty
,
testRepoURL
);
score
=
Math
.
round
(
score
*
10.0f
)
/
10.0f
;
return
score
;
// Filter out all advanced topics in any case
}
List
<
ExerciseCompetencyProfile
>
filteredExercises
=
exerciseCompetencyProfiles
.
stream
()
.
filter
(
testProfile
->
topicOrder
.
get
(
testProfile
.
exerciseTopicName
)
<=
currentTopicIndex
)
.
collect
(
Collectors
.
toList
());
LOG
.
debug
(
"Filtered exercises by topic index. Remaining exercises count: {}"
,
filteredExercises
.
size
());
// Filter by difficulty according to success
if
(
isFullSuccess
(
unsuccessful
))
{
LOG
.
debug
(
"Detected full success, filtering exercises with difficulty >= {} and excluding current testRepoURL."
,
currentDifficulty
);
filteredExercises
=
filteredExercises
.
stream
()
.
filter
(
profile
->
profile
.
difficulty
>=
currentDifficulty
&&
!
testRepoURL
.
equals
(
profile
.
exerciseURL
))
.
collect
(
Collectors
.
toList
());
}
else
{
LOG
.
debug
(
"Detected partial success, filtering exercises with difficulty <= {}."
,
currentDifficulty
);
filteredExercises
=
filteredExercises
.
stream
()
.
filter
(
profile
->
profile
.
difficulty
<=
currentDifficulty
)
.
collect
(
Collectors
.
toList
());
}
LOG
.
debug
(
"Filtered exercises count after difficulty filter: {}"
,
filteredExercises
.
size
());
return
filteredExercises
;
}
public
static
boolean
isFullSuccess
(
float
[]
unsuccessful
)
{
LOG
.
debug
(
"Checking for full success. Unsuccessful array: {}"
,
unsuccessful
);
for
(
float
value
:
unsuccessful
)
{
if
(
value
!=
0.0f
)
{
LOG
.
debug
(
"Found non-zero value in unsuccessful array: {}. Returning false for full success."
,
value
);
return
false
;
}
}
LOG
.
debug
(
"All values in unsuccessful array are zero. Returning true for full success."
);
return
true
;
}
public
static
float
calculateScore
(
ExerciseCompetencyProfile
exerciseProfile
,
float
[]
unsuccessful
,
Map
<
String
,
Integer
>
topicOrder
,
float
currentDifficulty
)
{
LOG
.
debug
(
"Starting calculateScore for exercise: {}, difficulty: {}, currentDifficulty: {}"
,
exerciseProfile
.
exerciseName
,
exerciseProfile
.
difficulty
,
currentDifficulty
);
float
score
=
1.0f
;
LOG
.
debug
(
"Initial score set to 1.0"
);
// Competency profile difference to not fully achieved competencies component
for
(
int
i
=
0
;
i
<
exerciseProfile
.
competencyAssessments
.
length
-
1
;
i
++)
{
float
adjustment
=
exerciseProfile
.
competencyAssessments
[
i
]
*
unsuccessful
[
i
];
score
+=
adjustment
;
LOG
.
debug
(
"Adjusted score by competency index {}: {}, new score: {}"
,
i
,
adjustment
,
score
);
}
// Difficulty component
float
difficultyComponent
=
exerciseProfile
.
difficulty
*
(
0.5f
+
Math
.
abs
(
currentDifficulty
-
exerciseProfile
.
difficulty
));
score
=
score
*
difficultyComponent
;
LOG
.
debug
(
"Applied difficulty component: {}, updated score: {}"
,
difficultyComponent
,
score
);
// Topic component
float
topicMultiplier
=
topicOrder
.
get
(
exerciseProfile
.
exerciseTopicName
);
score
*=
topicMultiplier
;
LOG
.
debug
(
"Applied topic multiplier: {}, updated score: {}"
,
topicMultiplier
,
score
);
// Round score
score
=
Math
.
round
(
score
*
10.0f
)
/
10.0f
;
LOG
.
debug
(
"Final rounded score: {}"
,
score
);
return
score
;
}
}
}
\ No newline at end of file
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment
Menu
Explore
Projects
Groups
Snippets