Skip to content

Commit a3b2cdd

Browse files
committed
feat: finalize RC9 setup/auth and e2e stabilization updates
1 parent df734c8 commit a3b2cdd

129 files changed

Lines changed: 3828 additions & 3730 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/src/main/java/jp/aegif/nemaki/api/v1/filter/ApiAuthenticationFilter.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,17 @@ private String getDefaultRepository() {
293293
}
294294
}
295295

296-
// Hard-coded fallback
297-
logger.warning("ApiAuthenticationFilter: No default repository found, using 'bedroom'");
298-
return "bedroom";
296+
// No default repository configured — return first key from RepositoryInfoMap if available
297+
if (repositoryInfoMap != null) {
298+
java.util.Set<String> keys = repositoryInfoMap.keys();
299+
if (keys != null && !keys.isEmpty()) {
300+
String first = keys.iterator().next();
301+
logger.warning("ApiAuthenticationFilter: No default repository configured, using first available: " + first);
302+
return first;
303+
}
304+
}
305+
logger.severe("ApiAuthenticationFilter: No repository available for authentication");
306+
return null;
299307
}
300308

301309
private void abortWithUnauthorized(ContainerRequestContext requestContext, String message) {

core/src/main/java/jp/aegif/nemaki/api/v1/resource/AuthResource.java

Lines changed: 155 additions & 42 deletions
Large diffs are not rendered by default.

core/src/main/java/jp/aegif/nemaki/api/v1/resource/GroupResource.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -701,20 +701,21 @@ private Folder getOrCreateSystemSubFolder(String repositoryId, String name) {
701701
Folder systemFolder = contentService.getSystemFolder(repositoryId);
702702

703703
if (systemFolder == null) {
704+
// Fallback: search for .system folder by path
704705
try {
705-
jp.aegif.nemaki.model.Content content = contentService.getContent(repositoryId, "34169aaa-5d6f-4685-a1d0-66bb31948877");
706+
jp.aegif.nemaki.model.Content content = contentService.getContentByPath(repositoryId, "/.system");
706707
if (content instanceof Folder) {
707708
systemFolder = (Folder) content;
708709
}
709710
} catch (Exception e) {
710-
logger.severe("Failed to find .system folder via fallback: " + e.getMessage());
711+
logger.severe("Failed to find .system folder via path fallback: " + e.getMessage());
711712
}
712-
713+
713714
if (systemFolder == null) {
714715
throw ApiException.internalError(".system folder not accessible");
715716
}
716717
}
717-
718+
718719
List<jp.aegif.nemaki.model.Content> children = contentService.getChildren(repositoryId, systemFolder.getId());
719720
if (CollectionUtils.isNotEmpty(children)) {
720721
for (jp.aegif.nemaki.model.Content child : children) {
@@ -723,13 +724,13 @@ private Folder getOrCreateSystemSubFolder(String repositoryId, String name) {
723724
}
724725
}
725726
}
726-
727+
727728
PropertiesImpl properties = new PropertiesImpl();
728729
properties.addProperty(new PropertyStringImpl("cmis:name", name));
729730
properties.addProperty(new PropertyIdImpl("cmis:objectTypeId", "cmis:folder"));
730731
properties.addProperty(new PropertyIdImpl("cmis:baseTypeId", "cmis:folder"));
731-
732-
return contentService.createFolder(new SystemCallContext(repositoryId), repositoryId,
732+
733+
return contentService.createFolder(new SystemCallContext(repositoryId), repositoryId,
733734
properties, systemFolder, null, null, null, null);
734735
}
735736

core/src/main/java/jp/aegif/nemaki/api/v1/resource/UserResource.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -688,15 +688,16 @@ private Folder getOrCreateSystemSubFolder(String repositoryId, String name) {
688688
Folder systemFolder = contentService.getSystemFolder(repositoryId);
689689

690690
if (systemFolder == null) {
691+
// Fallback: search for .system folder by path
691692
try {
692-
jp.aegif.nemaki.model.Content content = contentService.getContent(repositoryId, "34169aaa-5d6f-4685-a1d0-66bb31948877");
693+
jp.aegif.nemaki.model.Content content = contentService.getContentByPath(repositoryId, "/.system");
693694
if (content instanceof Folder) {
694695
systemFolder = (Folder) content;
695696
}
696697
} catch (Exception e) {
697-
logger.severe("Failed to find .system folder via fallback: " + e.getMessage());
698+
logger.severe("Failed to find .system folder via path fallback: " + e.getMessage());
698699
}
699-
700+
700701
if (systemFolder == null) {
701702
throw ApiException.internalError(".system folder not accessible");
702703
}

core/src/main/java/jp/aegif/nemaki/cmis/aspect/type/impl/TypeManagerImpl.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,9 @@ private void initGlobalTypes(){
359359
if (log.isDebugEnabled()) {
360360
log.debug("TYPES cache initialized/refreshed with keys: " + TYPES.keySet());
361361
}
362-
log.info("*** DIAGNOSIS: TYPES cache initialized/refreshed with keys: " + TYPES.keySet() + " ***");
363-
boolean hasBedroomTypes = TYPES.containsKey("bedroom");
364-
if (log.isDebugEnabled()) {
365-
log.debug("TYPES cache contains 'bedroom': " + hasBedroomTypes);
366-
}
367-
log.info("*** DIAGNOSIS: TYPES cache contains 'bedroom': " + hasBedroomTypes + " ***");
362+
String defaultRepoId = repositoryInfoMap.getDefaultRepositoryId();
363+
boolean hasDefaultRepoTypes = defaultRepoId != null && TYPES.containsKey(defaultRepoId);
364+
log.info("TYPES cache initialized with keys: " + TYPES.keySet() + ", default repo '" + defaultRepoId + "' present: " + hasDefaultRepoTypes);
368365
}
369366

370367
private void generate(){

core/src/main/java/jp/aegif/nemaki/cmis/factory/info/RepositoryInfo.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class RepositoryInfo extends org.apache.chemistry.opencmis.commons.impl.d
3838
//Custom info property
3939
private String nameSpace;
4040
private String archiveId;
41+
private boolean uiSelectable = true;
4142

4243
public void setup(){
4344
//Set changesOnType property
@@ -62,4 +63,12 @@ public String getArchiveId() {
6263
public void setArchiveId(String archiveId) {
6364
this.archiveId = archiveId;
6465
}
66+
67+
public boolean isUiSelectable() {
68+
return uiSelectable;
69+
}
70+
71+
public void setUiSelectable(boolean uiSelectable) {
72+
this.uiSelectable = uiSelectable;
73+
}
6574
}

core/src/main/java/jp/aegif/nemaki/cmis/factory/info/RepositoryInfoMap.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package jp.aegif.nemaki.cmis.factory.info;
22

3+
import java.util.ArrayList;
34
import java.util.HashMap;
45
import java.util.LinkedHashMap;
56
import java.util.LinkedHashSet;
@@ -73,6 +74,48 @@ public String getArchiveId(String repositoryId){
7374
return map.get(repositoryId).getArchiveId();
7475
}
7576

77+
/**
78+
* Check if the given repositoryId is an archive repository.
79+
*/
80+
public boolean isArchiveRepository(String repositoryId) {
81+
for (RepositoryInfo info : map.values()) {
82+
if (repositoryId.equals(info.getArchiveId())) {
83+
return true;
84+
}
85+
}
86+
return false;
87+
}
88+
89+
/**
90+
* Returns repository IDs where uiSelectable=true and not an archive repository.
91+
* Used for Login dropdown.
92+
*/
93+
public List<String> getUiSelectableKeys() {
94+
List<String> result = new ArrayList<>();
95+
for (Map.Entry<String, RepositoryInfo> entry : map.entrySet()) {
96+
String repoId = entry.getKey();
97+
RepositoryInfo info = entry.getValue();
98+
if (info.isUiSelectable() && !isArchiveRepository(repoId)) {
99+
result.add(repoId);
100+
}
101+
}
102+
return result;
103+
}
104+
105+
/**
106+
* Returns all non-archive repository IDs.
107+
* Used for administration purposes.
108+
*/
109+
public List<String> getMainRepositoryKeys() {
110+
List<String> result = new ArrayList<>();
111+
for (String repoId : map.keySet()) {
112+
if (!isArchiveRepository(repoId)) {
113+
result.add(repoId);
114+
}
115+
}
116+
return result;
117+
}
118+
76119
public RepositoryInfo getSuperUsers(){
77120
return map.get(this.superUsersId);
78121
}
@@ -92,10 +135,9 @@ public String getDefaultRepositoryId() {
92135
if (firstRepositoryId != null) {
93136
return firstRepositoryId;
94137
}
95-
// Fallback: LinkedHashMap preserves insertion order, so this is also deterministic
96-
Set<String> repositoryIds = keys();
97-
if (repositoryIds != null && !repositoryIds.isEmpty()) {
98-
return repositoryIds.iterator().next();
138+
// Fallback: use map.keySet() directly (NOT keys()) to avoid mutual recursion
139+
if (!map.isEmpty()) {
140+
return map.keySet().iterator().next();
99141
}
100142
return null;
101143
}
@@ -228,6 +270,8 @@ private void modifyInfo(Map<String, String> setting, RepositoryInfo info){
228270
info.setNameSpace(val);
229271
}else if(key.equals("archive")){
230272
info.setArchiveId(val);
273+
}else if(key.equals("uiSelectable")){
274+
info.setUiSelectable(Boolean.parseBoolean(val));
231275
}
232276
}
233277
}

core/src/main/java/jp/aegif/nemaki/dao/impl/couch/ContentDaoServiceImpl.java

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,20 +1553,92 @@ public PatchHistory getPatchHistoryByName(String repositoryId, String name) {
15531553

15541554
@Override
15551555
public Configuration getConfiguration(String repositoryId) {
1556-
// CRITICAL: Minimal fallback implementation for Spring initialization
1557-
// ViewQuery functionality temporarily disabled during Cloudant migration
1558-
1559-
// Return a basic Configuration object to allow Spring initialization
15601556
Configuration config = new Configuration();
1561-
config.setId("default_config");
1557+
config.setId("config_" + repositoryId);
15621558
config.setType("configuration");
1563-
1564-
// Set basic timestamps
15651559
config.setCreated(new GregorianCalendar());
15661560
config.setModified(new GregorianCalendar());
15671561
config.setCreator("system");
15681562
config.setModifier("system");
1569-
1563+
1564+
try {
1565+
// All configuration documents are stored in nemaki_conf DB
1566+
CloudantClientWrapper confClient = connectorPool.getClient(jp.aegif.nemaki.util.constant.SystemConst.NEMAKI_CONF_DB);
1567+
if (confClient == null) {
1568+
if (log.isDebugEnabled()) {
1569+
log.debug("nemaki_conf client not available for getConfiguration(" + repositoryId + ")");
1570+
}
1571+
return config;
1572+
}
1573+
1574+
// Query for type=configuration documents using Mango (_find) with bookmark pagination
1575+
Map<String, Object> selector = new HashMap<>();
1576+
selector.put("type", "configuration");
1577+
1578+
boolean isConfDb = jp.aegif.nemaki.util.constant.SystemConst.NEMAKI_CONF_DB.equals(repositoryId);
1579+
1580+
Map<String, Object> configMap = new HashMap<>();
1581+
String bookmark = null;
1582+
boolean hasMore = true;
1583+
1584+
while (hasMore) {
1585+
com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder builder =
1586+
new com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder()
1587+
.db(confClient.getDatabaseName())
1588+
.selector(selector)
1589+
.limit(200);
1590+
if (bookmark != null) {
1591+
builder.bookmark(bookmark);
1592+
}
1593+
1594+
com.ibm.cloud.cloudant.v1.model.FindResult result =
1595+
confClient.getClient().postFind(builder.build()).execute().getResult();
1596+
1597+
List<com.ibm.cloud.cloudant.v1.model.Document> docs = result.getDocs();
1598+
if (docs == null || docs.isEmpty()) {
1599+
break;
1600+
}
1601+
1602+
for (com.ibm.cloud.cloudant.v1.model.Document doc : docs) {
1603+
Map<String, Object> props = doc.getProperties();
1604+
if (props == null) continue;
1605+
1606+
String key = props.get("key") != null ? props.get("key").toString() : null;
1607+
Object value = props.get("value");
1608+
if (key == null) continue;
1609+
1610+
String docRepoId = props.get("repositoryId") != null ? props.get("repositoryId").toString() : null;
1611+
1612+
if (isConfDb) {
1613+
// Global config: include docs without repositoryId
1614+
if (docRepoId == null) {
1615+
configMap.put(key, value);
1616+
}
1617+
} else {
1618+
// Repo-specific config: include docs with matching repositoryId
1619+
if (repositoryId.equals(docRepoId)) {
1620+
configMap.put(key, value);
1621+
}
1622+
}
1623+
}
1624+
1625+
bookmark = result.getBookmark();
1626+
hasMore = (docs.size() == 200 && bookmark != null);
1627+
}
1628+
1629+
config.setConfiguration(configMap);
1630+
1631+
if (log.isDebugEnabled()) {
1632+
log.debug("getConfiguration(" + repositoryId + ") loaded " + configMap.size() + " entries: " + configMap.keySet());
1633+
}
1634+
1635+
} catch (Exception e) {
1636+
// During startup, nemaki_conf may not be available yet — return empty config
1637+
if (log.isDebugEnabled()) {
1638+
log.debug("getConfiguration(" + repositoryId + ") failed (normal during startup): " + e.getMessage());
1639+
}
1640+
}
1641+
15701642
return config;
15711643
}
15721644

core/src/main/java/jp/aegif/nemaki/init/DatabasePreInitializer.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ public synchronized void initializeDatabases(String url, String user, String pas
748748
*
749749
* <p>Delegates to {@link StartupProbeService#discoverNemakiDatabases} which
750750
* reads {@code repositories.yml} as the authoritative source.
751-
* Falls back to the default list if the probe service is unavailable.
751+
* Falls back to legacy defaults if {@code -Dnemaki.startup.require-repositories-yml=false}.
752752
*/
753753
private List<String> getRepositoryDbNames() {
754754
if (startupProbeService != null) {
@@ -758,11 +758,25 @@ private List<String> getRepositoryDbNames() {
758758
if (discovered != null && !discovered.isEmpty()) {
759759
return discovered;
760760
}
761+
} catch (IllegalStateException e) {
762+
// Propagate fail-fast from StartupProbeService
763+
throw e;
761764
} catch (Exception e) {
762-
log.debug("getRepositoryDbNames: StartupProbeService discovery failed: " + e.getMessage());
765+
log.warn("getRepositoryDbNames: StartupProbeService discovery failed: " + e.getMessage());
763766
}
764767
}
765-
return Arrays.asList("bedroom", "bedroom_closet", "canopy", "canopy_closet", "nemaki_conf");
768+
769+
// Feature flag: allow legacy fallback during migration period
770+
String requireYaml = System.getProperty("nemaki.startup.require-repositories-yml", "true");
771+
if ("false".equalsIgnoreCase(requireYaml)) {
772+
log.warn("getRepositoryDbNames: StartupProbeService unavailable — using legacy default list");
773+
return Arrays.asList("bedroom", "bedroom_closet", "canopy", "canopy_closet", "nemaki_conf");
774+
}
775+
776+
throw new IllegalStateException(
777+
"Cannot determine repository databases: StartupProbeService is unavailable. " +
778+
"Ensure repositories.yml is properly configured. " +
779+
"Set -Dnemaki.startup.require-repositories-yml=false to use legacy defaults during migration.");
766780
}
767781

768782
/**

core/src/main/java/jp/aegif/nemaki/init/StartupProbeService.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,10 @@ public List<RepositoryOverview> probeRepositories(String url, String user, Strin
379379
* <li>Parse {@code repositories.yml} (path from {@code -Drepositories.yml}
380380
* system property, or {@code PropertyManager} key {@code repository.definition})
381381
* → extract {@code id} (main DB) and {@code archive} (archive DB) per repository</li>
382-
* <li>Fallback: fixed default list ({@code bedroom, bedroom_closet, canopy, canopy_closet, nemaki_conf})</li>
382+
* <li>If repositories.yml cannot be loaded and
383+
* {@code -Dnemaki.startup.require-repositories-yml=false},
384+
* fall back to the legacy default list</li>
385+
* <li>Otherwise throw IllegalStateException</li>
383386
* </ol>
384387
*
385388
* <p>{@code nemaki_conf} is always included.
@@ -388,18 +391,32 @@ public List<RepositoryOverview> probeRepositories(String url, String user, Strin
388391
* (retained for API compatibility with existing callers).
389392
*
390393
* @return ordered list of database names (main repos first, then their archives, then nemaki_conf)
394+
* @throws IllegalStateException if repositories.yml cannot be loaded and fail-fast is enabled
391395
*/
392396
public List<String> discoverNemakiDatabases(String url, String user, String pass) {
393-
// 1. Try repositories.yml
397+
// Try repositories.yml (authoritative source)
394398
List<String> fromYaml = loadDbNamesFromRepositoriesYaml();
395399
if (fromYaml != null && !fromYaml.isEmpty()) {
396400
log.info("discoverNemakiDatabases: loaded from repositories.yml: " + fromYaml);
397401
return fromYaml;
398402
}
399403

400-
// 2. Fallback to fixed list
401-
log.info("discoverNemakiDatabases: falling back to default database list");
402-
return Arrays.asList("bedroom", "bedroom_closet", "canopy", "canopy_closet", "nemaki_conf");
404+
// Feature flag: allow legacy fallback during migration period
405+
// Set -Dnemaki.startup.require-repositories-yml=false to use legacy default list
406+
String requireYaml = System.getProperty("nemaki.startup.require-repositories-yml", "true");
407+
if ("false".equalsIgnoreCase(requireYaml)) {
408+
log.warn("discoverNemakiDatabases: repositories.yml not found — using legacy default list " +
409+
"(nemaki.startup.require-repositories-yml=false)");
410+
return Arrays.asList("bedroom", "bedroom_closet", "canopy", "canopy_closet", "nemaki_conf");
411+
}
412+
413+
// repositories.yml is required — fail explicitly
414+
String yamlPath = resolveRepositoriesYamlPath();
415+
String msg = "repositories.yml could not be loaded (resolved path: " + yamlPath + "). " +
416+
"This file is required for NemakiWare to determine which databases to manage. " +
417+
"Set -Dnemaki.startup.require-repositories-yml=false to use legacy defaults during migration.";
418+
log.error(msg);
419+
throw new IllegalStateException(msg);
403420
}
404421

405422
/**

0 commit comments

Comments
 (0)