I have an issue where I'm using a common class that many other apps are using to standardize some logic and reduce duplicate code. Most classes using this Operations
class only ever need CRUD methods. However, in this specific case I need to call a 5th method, execute
. Although, I don't want to repeat the logic that is defined inside the on
method.
How would you recommend I can call the onExecute
method via the same flow? If I can.
Ideally I would like to be able to do something like:
MyOperation
methods are invoked.I've shared the class structure below. I had to obscure the code to protect privacy agreements. If you need more clarification on the issue then please let me know and I can try to elaborate further.
// Common Code Package
public abstract class Operations {
public enum MyOperation {
CREATE, RETRIEVE, UPDATE, DELETE
}
protected MyCustomObject custom;
protected MyOperation operation;
public void on(MyOperation operation, Object instance, MyCustomObject custom){
this.custom = custom;
this.operation = operation;
if(operation == null){
throw new IllegalArgumentException("'operation' is invalid");
}
if(instance == null){
throw new IllegalArgumentException("'instance' is invalid");
}
if(custom == null){
throw new IllegalArgumentException("'custom' is invalid");
}
/* Other Logic */
switch(operation){
case CREATE:
onCreate(instance);
break;
case RETRIEVE:
onRetrieve(instance);
break;
case UPDATE:
onUpdate(instance);
break;
case DELETE:
onDelete(instance);
break;
default:
throw new UnsupportedOperationException(String.format("'%s' Operation Not Supported!", operation));
}
/* More Logic */
if(!custom.isOkay()){
throw new IllegalStateException("Not Okay");
}
}
protected void onCreate(Object instance) {
throw new UnsupportedOperationException("Create Operation Not Supported!");
}
protected void onRetrieve(Object instance) {
throw new UnsupportedOperationException("Retrieve Operation Not Supported!");
}
protected void onUpdate(Object instance) {
throw new UnsupportedOperationException("Update Operation Not Supported!");
}
protected void onDelete(Object instance) {
throw new UnsupportedOperationException("Delete Operation Not Supported!");
}
}
--
// Local Code Package
@Service
public class UserOperations extends Operations {
public void onExecute(Object instance){
/* Different Logic */
SomeType type = (SomeType) instance;
if(type.someOtherField == null){
custom.setOkay(false);
}
}
@Override
protected void onCreate(Object instance){
/* Create Logic */
SomeType type = (SomeType) instance;
if(type.someField == null){
custom.setOkay(false);
}
}
}
--
// Local Code Package
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserOperations userOperations;
@RequestMapping(value = "/create", method = RequestMethod.POST)
public Object create(SomeType instance){
MyCustomObject custom = new MyCustomObject();
userOperations.on(MyOperation.CREATE, instance, custom);
return userService.create(instance);
}
@RequestMapping(value = "/execute", method = RequestMethod.POST)
public Object execute(){
MyCustomObject custom = new MyCustomObject();
// How??? MyOperation doesn't include EXECUTE
// userOperations.onExecute(instance); doesn't include logic inside `on` method
// userOperations.on(MyOperation.EXECUTE, instance, custom);
return userService.execute(instance);
}
}
I was thinking all night about this and decided I can accomplish what I want using reflection if I lookup the method by name. I got that working with some minor tweaks on both sides. I added backwards support for the enumerator to not disturb the other apps using the same underlying common code. The enumerator methods will definitely exist while any other methods may or may not exist depending on the extending class.
I don't love that the additional types aren't "type-safe", but I think it's still good. Let me know if you think of something better.
* Updated Code *
// Common Code Package
public abstract class Operations {
private static final Logger LOGGER = LoggerFactory.getLogger(Operations.class);
public enum MyOperation {
CREATE, RETRIEVE, UPDATE, DELETE
}
protected MyCustomObject custom;
protected String operation;
public void on(MyOperation operation, Object instance, MyCustomObject custom){
// Change 'operation' to Standard Case e.g. CREATE => Create
String lower = operation.name().toLowerCase();
String value = lower.substring(0, 1).toUpperCase() + lower.substring(1);
// Delegate Invocation
on(value, obj, custom);
}
public void on(String operation, Object instance, MyCustomObject custom){
this.custom = custom;
this.operation = operation;
if(operation == null){
throw new IllegalArgumentException("'operation' is invalid");
}
if(instance == null){
throw new IllegalArgumentException("'instance' is invalid");
}
if(custom == null){
throw new IllegalArgumentException("'custom' is invalid");
}
/* Other Logic */
try {
String methodName = String.format("on%s", operation);
this.getClass().getDeclaredMethod(methodName, Object.class).invoke(this, obj);
} catch(IllegalAccessException | InvocationTargetException | NoSuchMethodException | SecurityException e){
LOGGER.error("ERROR: {}", e);
}
/* More Logic */
if(!custom.isOkay()){
throw new IllegalStateException("Not Okay");
}
}
public void onCreate(Object instance) {
throw new UnsupportedOperationException("Create Operation Not Supported!");
}
public void onRetrieve(Object instance) {
throw new UnsupportedOperationException("Retrieve Operation Not Supported!");
}
public void onUpdate(Object instance) {
throw new UnsupportedOperationException("Update Operation Not Supported!");
}
public void onDelete(Object instance) {
throw new UnsupportedOperationException("Delete Operation Not Supported!");
}
}
--
// Local Code Package
@Service
public class UserOperations extends Operations {
public void onExecute(Object instance){
/* Different Logic */
SomeType type = (SomeType) instance;
if(type.someOtherField == null){
custom.setOkay(false);
}
}
@Override
public void onCreate(Object instance){
/* Create Logic */
SomeType type = (SomeType) instance;
if(type.someField == null){
custom.setOkay(false);
}
}
}
--
// Local Code Package
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserOperations userOperations;
@RequestMapping(value = "/create", method = RequestMethod.POST)
public Object create(SomeType instance){
MyCustomObject custom = new MyCustomObject();
userOperations.on(MyOperation.CREATE, instance, custom);
return userService.create(instance);
}
@RequestMapping(value = "/execute", method = RequestMethod.POST)
public Object execute(SomeType instance){
MyCustomObject custom = new MyCustomObject();
userOperations.on("Execute", instance, custom);
return userService.execute(instance);
}
}