package org.dcm4chee.conf.browser; import org.dcm4che3.conf.api.TCConfiguration; import org.dcm4che3.conf.api.internal.DicomConfigurationManager; import org.dcm4che3.conf.core.Nodes; import org.dcm4che3.conf.core.api.*; import org.dcm4che3.conf.core.api.internal.ConfigProperty; import org.dcm4che3.conf.core.util.PathFollower; import org.dcm4che3.conf.core.util.PathPattern; import org.dcm4che3.conf.dicom.DicomPath; import org.dcm4che3.net.*; import org.dcm4che3.net.hl7.HL7ApplicationExtension; import org.dcm4che3.conf.api.upgrade.ConfigurationMetadata; import org.dcm4chee.util.SoftwareVersionProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.event.Event; import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.ws.rs.*; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.util.*; @SuppressWarnings( "unchecked" ) @Path("/config") @Produces(MediaType.APPLICATION_JSON) public class ConfigRESTServicesServlet { public static final String NOTIFICATIONS_ENABLED_PROPERTY = "org.dcm4che.conf.notifications"; private static final PathPattern referencePattern = new PathPattern(Configuration.REFERENCE_BY_UUID_PATTERN); public static final Logger log = LoggerFactory.getLogger(ConfigRESTServicesServlet.class); public enum OnlineStatus { ONLINE, OFFLINE, UNSUPPORTED } // Archive private static final String DEVICE_NAME_PROPERTY = "org.dcm4chee.archive.deviceName"; private static final String DEF_DEVICE_NAME = "dcm4chee-arc"; // XDS public static final Map<String, String> XDS_REST_PATH = new HashMap<>(); static { XDS_REST_PATH.put("StorageConfiguration", "xds-rep-rs"); XDS_REST_PATH.put("XdsRegistry", "xds-reg-rs"); XDS_REST_PATH.put("XdsRepository", "xds-rep-rs"); XDS_REST_PATH.put("XCAiInitiatingGWCfg", "xcai-rs"); XDS_REST_PATH.put("XCAiRespondingGWCfg", "xcai-rs"); XDS_REST_PATH.put("XCAInitiatingGWCfg", "xca-rs"); XDS_REST_PATH.put("XCARespondingGWCfg", "xca-rs"); } private static class SimpleConfigChangeEvent implements ConfigChangeEvent { private static final long serialVersionUID = 1338043186323821619L; @Override public CONTEXT getContext() { return CONTEXT.CONFIG_CHANGE; } @Override public List<String> getChangedPaths() { ArrayList<String> strings = new ArrayList<String>(); strings.add("/"); return strings; } } public static class DeviceJSON { public String deviceName; public String deviceDescription; public String deviceUuid; public Collection<AppEntityJSON> appEntities; public Collection<String> deviceExtensions; public boolean manageable; public static class AppEntityJSON { public String name; public String uuid; } } public static class SchemasJSON { public SchemasJSON() { } public Map<String, Object> tcgroups; public Map<String, Object> metadata; public Map<String, Object> device; /** * Parent class name to map - simple class name to schema */ public Map<String, Map<String, Map>> extensions; public Map<String, Object> schemaForClassName = new HashMap<>(); } public static class ConfigObjectJSON { public ConfigObjectJSON() { } /** * Object here is either a primitive, an array, a list, or Map<String, Object> */ public Map<String, Object> rootConfigNode; public Map<String, Object> schema; } public static class ExtensionJSON { public ExtensionJSON() { } public String deviceName; /** * user-friendly name */ public String extensionName; /** * Classname that will also be used for de-serialization */ public String extensionType; /** * Can the user restart the device */ public boolean restartable; /** * Can the user reconfigure the device */ public boolean reconfigurable; public ConfigObjectJSON configuration; } @Inject @Manager DicomConfigurationManager configurationManager; @Inject Event<InternalConfigChangeEvent> internalConfigChangeEvent; @Inject Event<ConfigChangeEvent> configChangeEvent; @Inject Instance<SoftwareVersionProvider> versionProviderInstance; private void fireConfigUpdateNotificationIfNecessary() throws ConfigurationException { // fire only if notifications are disabled by a property. Normally they should not be, except special cases like IT testing if (!Boolean.valueOf(System.getProperty(NOTIFICATIONS_ENABLED_PROPERTY, "true"))) { // no need to refresh nodes - we are assuming non-cluster setup in case if notifications are disabled, // and in this case it is already refreshed while calling persistNode/removeNode //configurationManager.getConfigurationStorage().refreshNode("/"); internalConfigChangeEvent.fire(new InternalConfigChangeEvent()); configChangeEvent.fire(new SimpleConfigChangeEvent()); } } @GET @Path("/devices") @Produces(MediaType.APPLICATION_JSON) public List<DeviceJSON> listDevices() throws ConfigurationException { List<DeviceJSON> list = new ArrayList<DeviceJSON>(); for (String deviceName : configurationManager.listDeviceNames()) { Device d = configurationManager.findDevice(deviceName); DeviceJSON jd = new DeviceJSON(); jd.deviceName = deviceName; jd.deviceDescription = d.getDescription(); jd.deviceUuid = d.getUuid(); jd.manageable = false; jd.appEntities = new ArrayList<>(); for (ApplicationEntity applicationEntity : d.getApplicationEntities()) { DeviceJSON.AppEntityJSON ae = new DeviceJSON.AppEntityJSON(); ae.name = applicationEntity.getAETitle(); ae.uuid = applicationEntity.getUuid(); jd.appEntities.add(ae); } jd.deviceExtensions = new ArrayList<String>(); for (DeviceExtension de : d.listDeviceExtensions()) { jd.deviceExtensions.add(de.getClass().getSimpleName()); } list.add(jd); } return list; } @GET @Path("/device/{deviceName}") @Produces(MediaType.APPLICATION_JSON) public Map<String, Object> getDeviceConfig(@PathParam(value = "deviceName") String deviceName) throws ConfigurationException { return (Map<String, Object>) configurationManager.getConfigurationStorage().getConfigurationNode(DicomPath.devicePath(deviceName), Device.class); } @GET @Path("/transferCapabilities") @Produces(MediaType.APPLICATION_JSON) public Map<String, Object> getTransferCapabilitiesConfig() throws ConfigurationException { return (Map<String, Object>) configurationManager.getConfigurationStorage().getConfigurationNode(DicomPath.TC_GROUPS_PATH, TCConfiguration.class); } @POST @Path("/transferCapabilities") @Produces(MediaType.APPLICATION_JSON) public void setTransferCapabilitiesConfig(Map<String, Object> config) throws ConfigurationException { configurationManager.getConfigurationStorage().persistNode(DicomPath.TC_GROUPS_PATH, config, TCConfiguration.class); fireConfigUpdateNotificationIfNecessary(); } @GET @Path("/metadata") @Produces(MediaType.APPLICATION_JSON) public Map<String, Object> getMetadata() throws ConfigurationException { return (Map<String, Object>) configurationManager.getConfigurationStorage().getConfigurationNode(DicomConfigurationManager.METADATA_ROOT_PATH, ConfigurationMetadata.class); } @POST @Path("/metadata") @Produces(MediaType.APPLICATION_JSON) public void setMetadata(Map<String, Object> config) throws ConfigurationException { configurationManager.getConfigurationStorage().persistNode(DicomConfigurationManager.METADATA_ROOT_PATH, config, ConfigurationMetadata.class); fireConfigUpdateNotificationIfNecessary(); } @GET @Path("/exportFullConfiguration") @Produces(MediaType.APPLICATION_JSON) public Map<String, Object> getFullConfig() throws ConfigurationException { return configurationManager.getConfigurationStorage().getConfigurationRoot(); } @POST @Path("/importFullConfiguration") @Consumes(MediaType.APPLICATION_JSON) public void setFullConfig(Map<String, Object> config) throws ConfigurationException { configurationManager.getConfigurationStorage().persistNode(org.dcm4che3.conf.core.api.Path.ROOT, config, null); fireConfigUpdateNotificationIfNecessary(); } @DELETE @Path("/device/{deviceName}") @Produces(MediaType.APPLICATION_JSON) public void deleteDevice(@PathParam(value = "deviceName") String deviceName) throws ConfigurationException { if (deviceName.isEmpty()) throw new ConfigurationException("Device name cannot be empty"); configurationManager.getConfigurationStorage().removeNode(DicomPath.devicePath(deviceName)); fireConfigUpdateNotificationIfNecessary(); } @POST @Path("/device/{deviceName}") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response modifyDeviceConfig(@Context UriInfo ctx, @PathParam(value = "deviceName") String deviceName, Map<String, Object> config) throws ConfigurationException { if (deviceName.isEmpty()) throw new ConfigurationException("Device name cannot be empty"); configurationManager.getConfigurationStorage().persistNode(DicomPath.devicePath(deviceName), config, Device.class); fireConfigUpdateNotificationIfNecessary(); return Response.ok().build(); } @GET @Path("/pathByUUID/{uuid}") @Produces(MediaType.APPLICATION_JSON) public org.dcm4che3.conf.core.api.Path getPathByUUID(@PathParam(value = "uuid") String uuid) { return configurationManager.getConfigurationStorage().getPathByUUID(uuid); } /** * Returns a fully processed node, i.e. including defaults, hashes, etc * * @param pathStr * @return */ @GET @Path("/node") @Produces(MediaType.APPLICATION_JSON) public Object getConfigurationNode( @QueryParam(value = "path") String pathStr, @QueryParam( value = "class") String className ) throws ClassNotFoundException { org.dcm4che3.conf.core.api.Path path = org.dcm4che3.conf.core.api.Path.fromSimpleEscapedPath(pathStr); Class configurableClass; if (className != null) { configurableClass = Class.forName( className ); } else { ConfigProperty last = PathFollower.traceProperties(configurationManager.getTypeSafeConfiguration().getRootClass(), path).getLast(); configurableClass = last.isConfObject() ? last.getRawClass() : null; } return configurationManager.getConfigurationStorage().getConfigurationNode(path, configurableClass ); } @POST @Path("/node") @Produces(MediaType.APPLICATION_JSON) public void persistConfigurationNode( @QueryParam(value = "path") String pathStr, @QueryParam( value = "class") String className, Map<String, Object> config) throws ClassNotFoundException { org.dcm4che3.conf.core.api.Path path = org.dcm4che3.conf.core.api.Path.fromSimpleEscapedPath(pathStr); Class configurableClass; if (className != null) { configurableClass = Class.forName( className ); } else { ConfigProperty last = PathFollower.traceProperties(configurationManager.getTypeSafeConfiguration().getRootClass(), path).getLast(); configurableClass = last.isConfObject() ? last.getRawClass() : null; } configurationManager.getConfigurationStorage().persistNode(path, config, configurableClass); fireConfigUpdateNotificationIfNecessary(); } @DELETE @Path( "/node" ) public void removeConfigurationNode( @QueryParam( value = "path" ) String pathStr ) { configurationManager.getConfigurationStorage().removeNode( org.dcm4che3.conf.core.api.Path.fromSimpleEscapedPath( pathStr ) ); } @GET @Path("/schemas") @Produces(MediaType.APPLICATION_JSON) public SchemasJSON getSchema(@QueryParam(value = "class") List<String> classNames) throws ConfigurationException, ClassNotFoundException { SchemasJSON schemas = new SchemasJSON(); schemas.device = getSchemaForConfigurableClass(Device.class); schemas.tcgroups = getSchemaForConfigurableClass(TCConfiguration.class); schemas.metadata = getSchemaForConfigurableClass(ConfigurationMetadata.class); schemas.extensions = new HashMap<>(); HashMap<String, Map> deviceExtensions = new HashMap<String, Map>(); schemas.extensions.put("Device", deviceExtensions); HashMap<String, Map> aeExtensions = new HashMap<String, Map>(); schemas.extensions.put("ApplicationEntity", aeExtensions); HashMap<String, Map> hl7AppExtensions = new HashMap<String, Map>(); schemas.extensions.put("HL7Application", hl7AppExtensions); HashMap<String, Map> connectionExtensions = new HashMap<String, Map>(); schemas.extensions.put("Connection", connectionExtensions); for (Class<? extends DeviceExtension> deviceExt : configurationManager.getExtensionClassesByBaseClass(DeviceExtension.class)) deviceExtensions.put(deviceExt.getSimpleName(), getSchemaForConfigurableClass(deviceExt)); for (Class<? extends AEExtension> aeExt : configurationManager.getExtensionClassesByBaseClass(AEExtension.class)) aeExtensions.put(aeExt.getSimpleName(), getSchemaForConfigurableClass(aeExt)); for (Class<? extends HL7ApplicationExtension> hl7Ext : configurationManager.getExtensionClassesByBaseClass(HL7ApplicationExtension.class)) hl7AppExtensions.put(hl7Ext.getSimpleName(), getSchemaForConfigurableClass(hl7Ext)); for (Class<? extends ConnectionExtension> connExt : configurationManager.getExtensionClassesByBaseClass(ConnectionExtension.class)) { connectionExtensions.put(connExt.getSimpleName(), getSchemaForConfigurableClass(connExt)); } // generic schemas for ( String className : classNames ) { schemas.schemaForClassName.put( className, getSchemaForConfigurableClass(Class.forName( className ))); } return schemas; } private Map<String, Object> getSchemaForConfigurableClass(Class<?> clazz) throws ConfigurationException { return configurationManager.getVitalizer().getSchemaForConfigurableClass(clazz); } /** * For troubleshooting purposes - returns all the versions of software components defined in the deployment */ @GET @Path("/versions") @Produces(MediaType.APPLICATION_JSON) public Map<String, String> getVersions() { if (versionProviderInstance.isUnsatisfied()) { log.warn("No versions provider defined"); } Map<String, String> allVersions = new HashMap<>(); for (SoftwareVersionProvider softwareVersionProvider : versionProviderInstance) { allVersions.putAll(softwareVersionProvider.getAllVersions()); } return allVersions; } }